Summary: in this tutorial, you will learn about React context and how to use it to share state across your entire React app.
Introduction to React Context
React context is a feature in React that allows you to share the state across your entire React app (or part of it) without passing props down through every level of the component tree.
React context is useful when many components at different nesting levels must access a piece of state.
Here’s a step-by-step guide to implementing React context.
Step 1. Create a context
First, create a context using the createContext()
function from the react
library:
// MyContext.js
import { createContext } fronm 'react';
const MyContext = createContext();
Code language: JavaScript (javascript)
Step 2. Create a Provider component
The provider component will use the context object and provide the shared state to its child components.
// MyProvider.js
import { useState } from 'react';
import MyContext from './MyContext';
const MyProvider = ({ children }) => {
const [state, setState] = useState();
const shared = {state, setState };
return (
<MyContext.Provider value={shared}>
{children}
</MyContext.Provider>
);
};
export default MyProvider;
Code language: JavaScript (javascript)
In this example, the Provider
component will share the object (shared
) that includes the state
and setState
function to its child components.
Besides the state
and setState
function, you can share any functions by placing them in the shared object.
Step 3. Use the Provider component in your App
Wrap the component tree within the Provider component to make the context object available to all child components:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import MyProvider from './MyProvider';
const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);
root.render(
<MyProvider>
<App />
</MyProvider>
);
Code language: JavaScript (javascript)
In this example, the App
components and all of its child components will be able to access the MyContext
object shared by the Provider
component.
Step 4. Consume the Context in a component
The following illustrates how to consume (or use or access) the MyContext
object from a child component of the Provider
component:
import { useContext } from 'react';
import MyContext from './MyContext';
const MyComponent = () => {
const { state, setState } = useContext(MyContext);
return (
<div>
<p>{state.someValue}</p>
<button onClick={() => setState({ ...state, someValue: 'New Value' })}>
Update Value
</button>
</div>
);
};
export default MyComponent;
Code language: JavaScript (javascript)
Refactoring the Todo app using Context
Let’s refactor the Todo app and use the React context:
In this diagram:
- The App component will access the
getTodos
function from the context to fetch all the todo items. - The TodoList component will access the
todos
state to display all todo items. - The TodoShow component will access the
changeTodo
andremoveTodo
functions. - The TodoCreate component will access the
createTodo
function to create a new todo item.
1) Creating a React context
First, create a new directory called context
within the src
directory.
Second, create a todos.js
file that will store the context object and Provider
component:
import { createContext, useState } from 'react';
const TodosContext = createContext();
const Provider = ({ children }) => {
const [todos, setTodos] = useState([]);
const getTodos = async () => {
const url = 'http://localhost:5000/todos';
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const storedTodos = await response.json();
// Update the state
if (storedTodos) setTodos(storedTodos);
} catch (error) {
console.error('Error during GET request:', error);
}
};
const createTodo = async (title) => {
// call an API to create a new todo
const url = 'http://localhost:5000/todos';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: title,
completed: false,
}),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const responseData = await response.json();
const newTodo = { ...responseData };
// add the new todo to the list
const updatedTodos = [...todos, newTodo];
setTodos(updatedTodos);
} catch (error) {
console.error('Error creating a todo:', error);
}
};
const removeTodo = async (id) => {
// Delete the todo
const url = `http://localhost:5000/todos/${id}`;
try {
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
// Update the state
const updatedTodos = todos.filter((todo) => todo.id !== id);
setTodos(updatedTodos);
} catch (error) {
console.error('Error during DELETE request:', error);
throw error;
}
};
const changeTodo = async (id, newTitle, completed = false) => {
// Update todo
const url = `http://localhost:5000/todos/${id}`;
const data = { title: newTitle, completed };
try {
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const responseData = await response.json();
// Update the state
const updatedTodos = todos.map((todo) => {
if (todo.id === id) {
return { ...todo, ...responseData };
}
return todo;
});
setTodos(updatedTodos);
} catch (error) {
console.error('Error during PUT request:', error);
throw error;
}
};
const shared = { todos, getTodos, createTodo, removeTodo, changeTodo };
return (
<TodosContext.Provider value={shared}>{children}</TodosContext.Provider>
);
};
export default TodosContext;
export { Provider };
Code language: JavaScript (javascript)
How it works.
First, import the createContext
and useState
functions from the react library:
import { createContext, useState } from 'react';
Code language: JavaScript (javascript)
Second, create a new Context
object called TodosContext
using the createContext
function:
const TodosContext = createContext();
Code language: JavaScript (javascript)
Third, define the Provider component:
const Provider = ({ children }) => {
// ...
}
Code language: JavaScript (javascript)
Fourth, create a state for the Provider component using the useState
function:
const [todos, setTodos] = useState([]);
Code language: JavaScript (javascript)
Fifth, move the getTodos
, createTodo
, removeTodo
, and changeTodo
functions from the App component to the Provider component.
Sixth, define an object to be shared across components that state and functions:
const shared = { todos, getTodos, createTodo, removeTodo, changeTodo };
Code language: JavaScript (javascript)
Seventh, pass the shared object to the value
prop of the Provider
component and return the Provider component:
return (
<TodosContext.Provider value={shared}>{children}</TodosContext.Provider>
);
Code language: JavaScript (javascript)
Finally, export the TodosContext
using the default export and Provider component using named export:
export default TodosContext;
export { Provider };
Code language: JavaScript (javascript)
Use the Provider component
Modify the index.js
to wrap the App
component with the Provider
component in the index.js
file:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from './context/todos';
const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);
root.render(
<Provider>
<App />
</Provider>
);
Code language: JavaScript (javascript)
How it works.
First, import the Provider
component from the context:
import { Provider } from './context/todos';
Code language: JavaScript (javascript)
Second, wrap the App
component with the Provider
component:
root.render(
<Provider>
<App />
</Provider>
);
Code language: JavaScript (javascript)
The App
component and all child components will be able to access the TodosContext
object provided by the Provider
component.
Modifying App component
Change the App
component to the following:
import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';
import { useEffect, useContext } from 'react';
import TodosContext from './context/todos';
import './App.css';
const App = () => {
const { getTodos } = useContext(TodosContext);
useEffect(() => {
getTodos();
}, []);
return (
<main className="main">
<h1>
React Todo <span>Streamline Your Day, the React Way!</span>
</h1>
<TodoList />
<TodoCreate />
</main>
);
};
export default App;
Code language: JavaScript (javascript)
How it works.
First, import the TodosContext
object:
import TodosContext from './context/todos';
Code language: JavaScript (javascript)
Second, access the getTodos
function from the TodosContext
using the useContext()
function:
const { getTodos } = useContext(TodosContext);
Code language: JavaScript (javascript)
Third, call the getTodos function within the useEffect
hook:
useEffect(() => {
getTodos();
}, []);
Code language: JavaScript (javascript)
Modifying TodoList component
Modify the TodoList
component that uses the TodosContext
:
import TodoShow from './TodoShow';
import TodosContext from '../context/todos';
import { useContext } from 'react';
const TodoList = () => {
const { todos } = useContext(TodosContext);
const renderedTodos = todos.map((todo) => {
return <TodoShow key={todo.id} todo={todo} />;
});
return <ul className="todo-list">{renderedTodos}</ul>;
};
export default TodoList;
Code language: JavaScript (javascript)
How it works.
First, import the TodosContext
:
import TodosContext from '../context/todos';
Code language: JavaScript (javascript)
Second, import the useContext
function:
import { useContext } from 'react';
Code language: JavaScript (javascript)
Third, access the todos array from the TodosContext
:
const { todos } = useContext(TodosContext);
Code language: JavaScript (javascript)
Finally, pass each todo item to the TodoShow
component:
const renderedTodos = todos.map((todo) => {
return <TodoShow key={todo.id} todo={todo} />;
});
Code language: JavaScript (javascript)
Modifying TodoShow component
Change the TodoShow.js
component to use the TodosContext
:
import { useState } from 'react';
import TodoEdit from './TodoEdit';
import EditIcon from '../edit.svg';
import DeleteIcon from '../delete.svg';
import { useContext } from 'react';
import TodosContext from '../context/todos';
const TodoShow = ({ todo }) => {
const [showEdit, setShowEdit] = useState(false);
const { removeTodo, changeTodo } = useContext(TodosContext);
const handleDelete = (e) => {
removeTodo(todo.id);
};
const handleEdit = (e) => {
setShowEdit(!showEdit);
};
const handleSubmit = (id, title) => {
changeTodo(id, title);
setShowEdit(false);
};
const handleDoubleClick = (e) => {
e.preventDefault();
changeTodo(todo.id, todo.title, !todo.completed);
setShowEdit(false);
};
if (showEdit)
return (
<li className="todo">
<TodoEdit todo={todo} onSubmit={handleSubmit} />
</li>
);
else {
return (
<li className="todo" onDoubleClick={handleDoubleClick}>
<p className={todo.completed ? 'completed' : 'open'}>{todo.title}</p>
<div className="actions">
<button onClick={handleDelete}>
<img src={DeleteIcon} title="Delete" />
</button>
<button onClick={handleEdit}>
<img src={EditIcon} title="Edit" />
</button>
</div>
</li>
);
}
};
export default TodoShow;
Code language: JavaScript (javascript)
How it works.
First, import the useContext
function from the react library and TodosContext
object from the todos.js
module:
import { useContext } from 'react';
import TodosContext from '../context/todos';
Code language: JavaScript (javascript)
Second, access the removeTodo
and changeTodo
functions from the context:
const { removeTodo, changeTodo } = useContext(TodosContext);
Code language: JavaScript (javascript)
Modify the TodoCreate component
Modify the TodoCreate
component to use the TodosContext
object:
import { useState, useContext } from 'react';
import TodosContext from '../context/todos';
const TodoCreate = () => {
const [todo, setTodo] = useState('');
const { createTodo } = useContext(TodosContext);
const handleSubmit = (e) => {
e.preventDefault();
createTodo(todo);
setTodo('');
};
const handleChange = (e) => {
setTodo(e.target.value);
};
return (
<form onSubmit={handleSubmit} className="todo-create">
<input
type="text"
name="todo"
id="todo"
placeholder="Enter a todo"
value={todo}
onChange={handleChange}
/>
</form>
);
};
export default TodoCreate;
Code language: JavaScript (javascript)
How it works.
First, import the TodosContext
object:
import TodosContext from './context/todos';
Code language: JavaScript (javascript)
Second, access the createTodo
function from the TodosContext
using the useContext()
function:
const { createTodo } = useContext(TodosContext);
Code language: JavaScript (javascript)
Third, call the createTodo
function to create a new todo item:
const handleSubmit = (e) => {
e.preventDefault();
createTodo(todo);
setTodo('');
};
Code language: JavaScript (javascript)
Download the project source code
Download the React Todo App using Context
Summary
- Use React context to share the state across your entire React app.