Introduction to React context API and useContext() hook.
Elo Elo, Hope you guys doing great, this post introduces you to react's context API
and useContext()
hook. we will learn about how to use React.CreateContext()
to create a store and access them using useContext()
Hook.
Credits section
image credits - @hannahmorgan7
What is context and where should we use it?
Ever in a situation where you have to pass props to a number of components to exchange data between components? well, it's known as props drilling.
Yes, props drilling is ok but in some places, props drilling will result in big messy code.
To solve these problems we can go with react context.
Context provides a way to pass data through the component tree without having to pass props down manually at every level. - reactjs.org
Let's get started by creating a react application using CRA.
create-react-app todo_context
yes we are following the to-do tradition LOL.
Add 2 new folders src/components
and src/context
let's concentrate on context first then we will create the components.
Add 2 new files inside the context folder provider.js
and store.js
now that we have created the required files we can start creating our first context.
Setup Context
store.js
import React from "react";
const Store = React.createContext({
todos: [],
addTodo: (todo) => {},
deleteTodo: (index) => {}
});
export default Store;
Ok what is happening here, we are creating a context store/object using react's createContext
function.
- we need
todo:[]
to store all the todos, for simplicity a single todo will look like the following.
{
text: "Finish that side project please ;-; "
}
-
addTodo
function will add a single todo item to the todo array. -
deleteTodo
function will remove a todo item from the list based on the index.
finally, make sure to export Store so that we can use it from elsewhere.
provider.js
import { useState } from "react";
import Store from "./store";
const TodoProvider = (props) => {
const [todos, setToDos] = useState([]);
const addTodo = (todo) => {
if (todo) {
setToDos([...todos, todo]);
}
console.log(todos);
};
const deleteTodo = (index) => {
if (todos[index]) {
const fliteredArr = todos.filter((element, i) => i !== index);
setToDos(fliteredArr);
}
};
return (
<Store.Provider
value={{
todos,
addTodo,
deleteTodo
}}
>
{props.children}
</Store.Provider>
);
};
export default TodoProvider;
The provider is just like any functional component that has state other functions.
as you can see we have a state const [todos, setToDos] = useState([]);
and 2 functions addTodo
and deleteTodo
lets see one by one.
todos
state will maintain the todo list since it's a state the changes to this will rerender the provider and its child components in our case the entire application (later we will wrap our application within this provider).
Tip: Always wrap what you want to rerender when the context change. since this may lead to lesser performance. having multiple atomic contexts is better than a huge single context for the entire application.
-
addTodo
anddeleteTodo
are for adding and deleting todo items. -
Now the
return
part here we are wrapping theprops.children
with<Store.provider value ={}/>
so the provider is responsible for enabling child components to access the context data and renders (if data changed). in the value attribute we are passing the state and functions.
Setup Components
Add 2 new files AddTodoCtrl.js
and TodoList.js
inside the components.
AddTodoCtrl.js
import { useContext, useState } from "react";
import Store from "../context/store";
const AddTodoCtrl = (props) => {
const [todo, setTodo] = useState("");
const { addTodo } = useContext(Store);
const handleOnClick = (event) => {
if (todo) {
addTodo(todo);
} else {
alert("please enter valid value");
}
};
const handleOnChange = (event) => {
if (event.target.value) {
setTodo(event.target.value);
}
};
return (
<div>
<input type="text" id="todoTxt" onChange={handleOnChange} />
<button onClick={handleOnClick}> + </button>
</div>
);
};
export default AddTodoCtrl;
- it's an average form definitely not a good one :P
useContext(Store)
is a react hook that will return the current value for that context. we need addTodo let us destructure that.- inside the onClick listener we are updating the todos list in the context with new todo item.
TodoList.js
import { useContext } from "react";
import Store from "../context/store";
const TodoList = (props) => {
const { deleteTodo,todos } = useContext(Store);
const removeItem = (index) => {
deleteTodo(index);
};
return (
<div className="list-container">
{todos.map((todo, index) => {
return (
<div
className="list-item"
onClick={() => removeItem(index)}
key={index}>
{todo}
</div>
);
})}
</div>
);
};
export default TodoList;
- This component needs 2 things from the context the
todos:[]
list data to show the list anddeleteTodo(index)
function to delete a todo item from the list.
App.js
Now that we finished all the components we need for this application its finally time to glue all things together.
import AddTodoCtrl from "./components/AddTodoCtrl";
import TodoList from "./components/TodoList";
import TodoProvider from "./context/provider";
import "./styles.css";
export default function App() {
return (
<TodoProvider>
<div className="App">
<h1>To do</h1>
<hr />
<TodoList />
<AddTodoCtrl />
</div>
</TodoProvider>
);
}
- Make sure to wrap all the JSX with
TodoProvider
without this you won't see the updated data in the UI.
Take styles.css content from here
Finally, run npm start
.
Working application at codesanbox
This is my first article in a while, I am happy to start blogging again.
Found any bugs or you have any suggestions to improve my writing let me know via Twitter or Instagram.