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.
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 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 and source code here.
Thanks for reading.
Top comments (1)
A tremendous help for me today thank you for the article!