Introduction
To follow through with this tutorial I highly recommend you go through my previous post where I discussed the 3 things needed to make a good API call.
Today, we are going deeper to talk about how you can get the most from combining context and custom hooks in your applications. If you do not know, context is the built-in state management tool in react from react v16.3.
Context was added as a way to solve the problem of prop drilling and make it easier to share data between components.
It is not a replacement for "proper" state management tools such as redux but is used to store light data or data that isn't frequently changing such as
theme (light or dark)
user data (authenticated user data)
location-specific data.
I am going to assume you know how to set up a react application from scratch
Getting started with context
after initializing your react application, inside the src
folder, create a new folder store
. this is where we are going to create our state.
inside the store
folder create a folder named user
this folder is going to contain all the files related to handling our user data.
inside the user
directory, create a new file user.js
.
in this file, we are going to create our context
import { createContext } from "react";
const userContext = createContext({
data: null,
loading: true,
loggedIn:false,
error: null,
});
export default userContext;
we import createContext
from react
and we initialize it passing in an object which represents our initial state.
next, we create a new file provider.js
inside the same directory. this file is going to contain the logic and actual state of our context.
import userContext from "./user";
export default function UserProvider({ children }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const controller = new AbortController();
async function fetchAll() {
await axios("", {
method: "GET",
signal: controller.signal,
})
.then((res) => {
setData(res.data);
})
.catch((err) => {
setError(error.message ?? "An error occured");
})
.finally(() => {
setLoading(false);
});
}
useEffect(() => {
fetchAll();
return () => controller.abort();
}, []);
const value = {
data,
loading,
error,
loggedIn: !!data,
};
return <userContext.Provider value={value}>{children}</userContext.Provider>;
}
if you do not understand how to make API requests, then this post is for you.
first, we import our context from our user.js
file then we create a component UserProvider
which expects a children
prop. We make our network request and update the necessary state. We return a provider userContext.Provider
which accepts a value
props which is an object containing the actual data that is to be used inside our application.
the data inside the context can only be accessed by the components which are its children
How to use context
import { useContext } from "react";
import UserProvider from "./store/user/provider";
import userContext from "./store/user/user";
function App() {
return (
<UserProvider>
<Test/>
</UserProvider>
);
}
function Test() {
const { loading } = useContext(userContext);
return <p>{loading ? "true" : "false"}</p>;
}
export default App;
to access the data in our context
we wrap the root node of the elements which share the data inside the provider
(in our case, our root node is the root of the application).
to access the data in our context, we need to make use of the useContext
hook and pass in the user context as a parameter to the function.
Introducing our custom hook
inside the user
directory, create a new file called useUser.js
import { useContext } from "react";
import userContext from "./user";
export default function useUser() {
const user = useContext(userContext);
return user;
}
all this hook does is access the data from context and return the data.
after this, we no longer need to import the useContext
hook and the userContext
everytime we need the user data. we just make use of the useUser
hook. This is illustrated below
import useUser from "./store/user/useUser";
function Test() {
const { loading } = useUser();
return <p>{loading ? "true" : "false"}</p>;
}
Summary
- Combining context and custom hooks improves code reusability and enforces the DRY principle