Using context and custom hooks like a pro

Using context and custom hooks like a pro

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