React Global State with useContext
Tags: react, contextapi, globalstate, usecontext
Published: 2020-12-11 22:21:30

Imagine your global state API looks like this.

```
const Index: React.FC = () => {
  const { loading, recipeList, getRandomRecipes} = useStore();
...
```
Just one hook that provides all you need from a global state handler. This could be achieved by using `Context` API natively provided by React 16.x.

According to the documentation, Context API is for avoiding prop drilling which means passing a prop down to deeply nested components through all its parents. You read more about his [here](https://reactjs.org/docs/context.html#before-you-use-context).

We will leverage the concept of the hook of React to make the consuming process of context more developer-friendly.

First and simple, we need to create a context. I usually create all files that relate to the global state inside the store folder that is in the src folder of the project.
```
|__src
    |__components
    |__store
        |__Context.ts
```
The code for context will look like this.

```
export const Context = React.createContext(defaultContext)
```
You can omit `defaultContex`, but it is good practice to use it in order to be able to isolate and test it.

So, now we have created our context. Let move to the main part which is creating an actual global state. There is nothing fancy here just a simple custom hook with your states. I usually call it `useGlobalState`.

```
|__src
    |__components
    |__store
        |__Context.ts
        |__useGlobalState.ts
```

After creating the file we create the states that should be accessible from any component of our application and the methods to manipulate the state.

```
import { useState, useMemo } from "react";
import { makeApiRequest } from "../utils";

export const useGlobalState = () => {
  const [recipeList, setRecipeList] = useState(null);
  const [reviewBarOpen, setReviewBarOpen] = useState(false);
  const [loading, setLoading] = useState(true);

  const searchByName = useMemo(
    () => (keyword: string) => {
      makeApiRequest(
        `/api/search-by?keyword=${keyword}`,
        (data) => setRecipeList(data.meals),
        setLoading
      );
    },
    [setLoading]
  );

  const searchByIngredients = useMemo(
    () => (ingredients: string) => {
      makeApiRequest(
        `/api/filter-by?filterType=i&filterValue=${ingredients}`,
        (data) => setRecipeList(data.meals),
        setLoading
      );
    },
    [setLoading]
  );

  const openReviewBar = useMemo(() => () => 
                          setReviewBarOpen(true), [
                          setReviewBarOpen,
                        ]);

  const closeReviewBar = useMemo(() => () => 
                          setReviewBarOpen(false), [
                          setReviewBarOpen,
                        ]);
  const resetReviewState = useCallback(() => {
                            setReviewedRecipe(null);
                            closeReviewBar();
                           }, [closeReviewBar]);
  return {
    recipeList,
    searchByName,
    searchByIngredients,
    reviewBarOpen,
    resetReviewState,
  };
};

```

So, basically, what we are doing is exposing only those parts of the state and methods that should be accessible publically from child components.

The next step is optional but make this solution more elegant. I create an additional provider component.
```
|__src
    |__components
    |__store
        |__Context.ts
        |__useGlobalState.ts
        |__StateProvider.ts
```

```
import React from "react";
import { Context } from "./Context";
import { useGlobalState } from "./useGlobalState";

export const StateProvider: React.FC = ({ children }) => {
  const store = useGlobalState();

  return (
     <Context.Provider value={store}>
        {children}
     </Context.Provider>
  )
};

```
Next, I wrap my application to the `StateProvider`, If not I cannot access global in children components.

```
import React from "react";

export const App= ({children})=>{
 return (
    <StateProvider>
      <Layout>
        {children}
      </Layout>
    </StateProvider>
  );
};
```

Lastly, I implement a custom hook to consume the global state.

```
|__src
    |__components
    |__store
        |__Context.ts
        |__useGlobalState.ts
        |__useStateProvider.ts
        |__useStore.ts
```

```
import { useContext } from "react";
import { Context } from "./Context";

export const useStore = () => {
  const store = useContext(Context);
  return store;
};
```

That's it, our global state is ready to use. Now, you just need to call the hook and consume the provided API.

```
import React, { useEffect } from "react";
import { useStore } from "@/store";

export const ByName: React.FC = () => {
  const { searchByName, getRandomRecipes } = useStore();
  const [value, setValue] = useState("");

  useEffect(() => {
    if (!Boolean(value.trim())) {
      getRandomRecipes();
    }
  }, [value, getRandomRecipes]);

 ...
```

As a result, This keeps your components clean, only one place to look for bugs regarding your global state, and, also,  isolates the data layer from the view layer which makes it easy to test this kind of application.

## Testing

If you wonder how you would test your components that consume global state directly check out my other [post](https://dev.to/rufataliy/testing-react-applications-that-use-context-global-state-45n4) where I walk you through process.
Let me know what's your implementation of the global state.

By the way, if you wanted to check the app where I implemented this style you can view it [here](https://recippy.rufat.tech/) and source code [here](https://github.com/rufataliy/Recippy).

Thanks for reading.