Guide to useContext with Blog Example
Why is useContext useful
The useContext hook is a really useful way to share data between unconnected components in the component tree. Previously this could be done by lifting the component state up to the highest shared parent component and then passing props down the separate sides of the tree.
This example shows that 2 separate child nodes need access to the count
prop. To do this the state is lifted up to the very top level and then passed down to each child that needs it via props.
This is a useful technique and I’ve shown a code example of how to do this in practice here but there are some issues:
- a lot of state could end up being stored at the very top level which could get quite complicated.
- The props are passed through intermediary components that do not need to know about them.
- In more complex trees with lots of nested components you could find yourself passing props down many levels which is ultimately hard to maintain and could cause bugs.
useContext
on the other hand lets the components that need the state access it directly from the shared context.
Simple Blog Example
To create a blog site we will need a page that displays that has components to articles and a component to add new articles.
app.js
import React, { createContext } from 'react';
import AddArticle from './components/AddArticle';
import Articles from './containers/Articles';function App() {
const ArticleContext = createContext();
const articles = [
{ id: 1, title: "Post 1", body: "Test Article 1"},
{ id: 2, title: "Post 2", body: "Test Article 2" }
]; return (
<ArticleContext.Provider value={articles}>
<AddArticle />
<Articles />
</ArticleContext.Provider>
);
}export default App;
In order to access the articles in any child component the code would look something like this:
articles.js
import React, { useContext } from 'react';
import { ArticleContext } from '../app';
import Article from '../components/Article';const Articles = () => {
const articles = useContext(ArticleContext);
return (
<div>
{ articles.map((article) => (
<Article key={article.id} article={article} />
))}
</div>
);
};export default Articles;
To access the articles you simply have to use the hook with the context you created useContext(ArticleContext)
. This works for displaying articles but what about when we need to update the context from a child component. We want to do this in AddArticles
so that any new article is displayed.
Blog Example with useReducer
One way to update the context is to utilise the useReducer
hook to update the global state. A full working example can be found on Github. We would need to update our app.js
to have a new initial state.
app.js
import React, { createContext } from 'react';
import AddArticle from './components/AddArticle';
import Articles from './containers/Articles';const reducer = (state, action) => {
switch(action.type) {
case "ADD_ARTICLE":
return [
...state,
{
id: Math.random(),
title: action.article.title,
body: action.article.body
}
];
default:
return state;
}
}function App() {
const ArticleContext = createContext();
const [ articles, dispatch ] = useReducer( reducer, [
{ id: 1, title: "Test Article 1"},
{ id: 2, title: "Post 2", body: "Test Article 2" }
]); return (
<ArticleContext.Provider value={{ articles, dispatch }}>
<AddArticle />
<Articles />
</ArticleContext.Provider>
);
}export default App;
Now the state has to be accessed in a slightly different way in Articles
since it is now an object const { articles } = useContext(ArticleContext);
To add articles to your site you could add a simple form component:
AddArticle.js
import React, { useState, useContext } from 'react';
import { ArticleContext } from '../context/articleContext';const AddArticle = () => {
const { dispatch } = useContext(ArticleContext);
const [ article, setArticle ] = useState(); const handleArticleData = (e) => {
setArticle({
...article,
[e.target.id]: e.target.value,
})
} const addNewArticle = (e) => {
e.preventDefault(); # Add article to context
dispatch({ type: "ADD_ARTICLE", article }); # reset form values
e.target.title.value = "";
e.target.body.value = "";
}; return (
<form onSubmit={ addNewArticle }>
<input
type='text'
id='title'
placeholder='Title'
onChange={ handleArticleData }
/>
<input
type='text'
id='body'
placeholder='Body'
onChange={ handleArticleData }
/>
<button>Add Article</button>
</form>
);
};export default AddArticle;
That’s it, you have a simple blog site using useContext and useReducer. This is somewhat limited as the articles are not stored permanently and do not persist on refreshes. To fix this in addNewArticle()
you could create a call to an external DB to store your article and the initial state in useReducer
should call this same resource to get the up to date list of blog articles.