Create a react app using context, hooks and reducers
Yes, I know what you're thinking. This isn't just another todo list tutorial, is it?
In this post, I'll be explaining a little advanced concept called React _ Context _ and how to use it in an application by building a very basic todo list app.
Expectations
This post assumes a basic understanding of the react framework.
Let's get started
What is Context?
According to official react documentation
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Why/When do we need to use Context?
React documentation to the rescue again
In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.
Okay, enough with the introduction. Now we build a basic react app using create-react-app starter kit.
For those who would like to see the finished app, here is a preview of the complete code
First create a Todo.js
file inside the src
directory and add the following code to it. This component will render a button and a controlled input
import React, { useState } from "react";
export default function Todo() {
const [todo, setTodo] = useState("");
return (
<form>
<input
type="text"
name="todo"
value={todo}
onChange={(e) => setTodo(e.target.value)}
/>
<button>Add todo </button>
</form>
);
}
Next create a TodoList.js
file and the src
directory. This component will render our todo list, but for now it simply renders a h1
heading for the list.
import React from "react";
export default function TodoList() {
return (
<div>
<h1> Todo List </h1>
</div>
);
}
Now we are getting to the interesting part.
To use Context in a react app, we need to create the context. So create a TodoContext.js
in the same src
directory.
Add the following line of code to it. Yes, I know we don't have a ./reducer.js
file yet. Don't worry, we will create it soon.
import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "./reducer";
Create a context named todoContext
by adding the next line of code
export let todoContext = createContext({});
Next create function component Provider
and pass down children
as props to it.
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
const values = {
state,
dispatch
};
return (
<>
<todoContext.Provider value={values}>
{children}
</todoContext.Provider>
</>
);
}
let's explain the code above.
- useReducer - The useReducer Hook is similar to the useState Hook. It allows for custom state logic. If you find yourself keeping track of multiple pieces of state that rely on complex logic, useReducer may be useful. This hook accepts two arguments, a reducer and an initial state.
- Our
Provider
component returns a context.Provider component which accepts a value prop and passes it down to the children of ourProvider
component.
Here is the full code for the TodoContext.js
import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "./reducer";
export let todoContext = createContext({});
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
const values = {
state,
dispatch
};
return (
<>
<todoContext.Provider value={values}>
{children}
</todoContext.Provider>
</>
);
}
Let's create our reducer.js
file and add the code below to it
import { ADD_TODO } from "./constants";
export const initialState = {
todos: []
};
const reducer = (state, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload]
};
default:
return state;
}
};
export default reducer;
The above code creates an initial state and initializes todos to an empty array. We then added a reducer function, a reducer function accepts a state and action as arguments and returns the updated state based on the type of action dispatched, which in our case we have an action called ADD_TODO that will be defined in constants.js
.
Create the file constants.js
and add following code to it
export const ADD_TODO = "ADD TODO";
As we can see, constants.js simply creates and exports a string variable.
Next we update the code inside our app.js
file by importing the TodoContext.js
, Todo.js
and TodoList.js
component.
app.js
now should look like this
import React, { Component } from "react";
import Todo from "./Todo";
import TodoList from "./TodoList";
import Provider from "./TodoContext";
export default class App extends Component {
render() {
return (
<div className="App">
<Provider>
<Todo />
<TodoList />
</Provider>
</div>
);
}
}
Since we have wrapped our components in a Provider component, we can now be sure that we have access to the context in our Todo.js
and TodoList.js
.
We can update out Todo.js
file to now access the context value and also create a function addTodoHandler
that dispatches the ADD_TODO action type.
Below is the complete code for Todo.js
import React, { useState, useContext } from "react";
import { todoContext } from "./TodoContext";
import { ADD_TODO } from "./constants";
export default function Todo() {
const [todo, setTodo] = useState("");
const { dispatch } = useContext(todoContext);
const addTodoHandler = (e) => {
e.preventDefault();
dispatch({ type: ADD_TODO, payload: todo });
setTodo("");
};
return (
<form>
<input
type="text"
name="todo"
value={todo}
onChange={(e) => setTodo(e.target.value)}
/>
<button onClick={addTodoHandler}>Add todo </button>
</form>
);
}
Cool. Now all we need to do is display our Todo whenever a user clicks the Add todo button. And we do this by mapping over our todos array defined in the reducer.js and returning the jsx.
Below is the complete code for the TodoList.js
import React, { useContext } from "react";
import { todoContext } from "./TodoContext";
export default function TodoList() {
const { state } = useContext(todoContext);
return (
<div>
<h1> Todo List </h1>
{state.todos.length > 0 &&
state.todos.map((todo, id) => {
return <h4 key={id}>{todo}</h4>;
})}
</div>
);
}
That's it. Now you have learnt what context means, when to use it and what a reducer is and most importantly, you've learnt how to implement them in an application, albeit a small one .
Disclaimer: To build a todo app like this one, using context and reducer is kind of an overkill and is unnecessary. But to better demonstrate and explain this concepts, I had to choose an example that won't be too complex to understand.
Edit: As suggested by BilalurRehman in the comments, it is not a very good practice to use index as key in the TodoList.js
file, it is better to use the
UUID() library.
Thank you for reading, you can reach out to me for suggestions or corrections.
can i get script for converting? team want to test if for website, check here https://gbchief.com/
Is a good idea to use this script to convert online files? I want to test it for my website, you can see here https://gbhouse.info/convert2mp3-net/
Hey Majid.
Your code is super cool and very simple. I like that. I just wanted to point out one thing, in your ‘TodoList.js’ you have used ‘index’ as key. Which is not recommended.
You can always use uuid() for that.
Thanks for the suggestion BilalurRehman.
Using uuid is an excellent suggestion, but I tried to keep the app as simple as possible and tried to avoid using any external libraries to allow readers focus on the main idea and concepts the post was written for.
I’ll add a note to the post to reflect your suggestion for users that may be interested.
I’m currently working on a more detailed post as a buildup on this, where I will definitely use the uuid as you suggested and add features like drag and drop and maybe firebase integration for persistence. My goal is for my post to help simplify react as much as possible to new users while serving as a mini cheatsheet for intermediate and more experienced once.
You can reach if you have more suggestions as regards the post or if you have a topic you’d like me to write on.
Will do. Thanks. (Y)