Explore States
import { createContext, useContext, useReducer, useState } from "react";
import { ApolloError, gql, useMutation, useQuery } from "@apollo/client";
#
State in one component only//using standard React stateconst Count = () => { const [count, setCount] = useState(0); const handleClick = () => setCount((pr) => pr + 1);
return <p onClick={handleClick}>{count}</p>;};
#
Component and its children//basic drillingconst Parent = () => { const [count, setCount] = useState(0); const handleClick = () => setCount(pr => pr + 1);
return ( <> <Children_1 count={count} /> <button onClick={handleClick}>Increase the count</button> <Children_2 count={count} /> </> );};
const Children_1 = (props: { count: number}) => { const { count } = props;
return <h3>{count}</h3>};
const Children_2 = (props: { count: number}) => { const { count } = props;
return <h3>{count \* 2}</h3>};
#
Avoid prop drilling using contextProp drilling is basically a situation when the same data is being sent at
almost every level due to requirements in the final level:
Parent
->Child_A
->Child_B
->Child_C
#
Example:Data needed to be sent from Parent_1
to ChildC_1
.
function Parent_1() { const [fName, setfName] = useState("firstName"); const [lName, setlName] = useState("LastName"); return ( <> <div>This is a Parent component</div> <br /> <ChildA_1 fName={fName} lName={lName} /> </> );}
type Props = { fName: string, lName: string };
function ChildA_1({ fName, lName }: Props) { return ( <> This is ChildA Component. <br /> <ChildB_1 fName={fName} lName={lName} /> </> );}
function ChildB_1({ fName, lName }: Props) { return ( <> This is ChildB Component. <br /> <ChildC_1 fName={fName} lName={lName} /> </> );}
function ChildC_1({ fName, lName }: Props) { return ( <> This is ChildC component. <br /> <h3> Data from Parent component is as follows:</h3> <h4>{fName}</h4> <h4>{lName}</h4> </> );}
#
useContext exampleData needed to be sent from Parent_2
to ChildC
.
The problem with Prop Drilling is that whenever data from the Parent
component will be needed, it would have to come from each level.
A better alternative to this is using useContext hook. The useContext hook
is based on Context API and works on the mechanism of Provider and Consumer.
Provider needs to wrap components inside Provider Components in which data
have to be consumed. Then in those components, using the useContext hook that
data needs to be consumed.
const ExampleContext = createContext < Props > { fName: "", lName: "" };
const Parent_2 = () => { const [fName, setfName] = useState("firstName"); const [lName, setlName] = useState("LastName");
return ( <ExampleContext.Provider value={{ fName, lName }}> <div>This is a Parent component</div> <br /> <ChildA /> </ExampleContext.Provider> );};
const ChildA = () => { return ( <> This is ChildA Component. <br /> <ChildB /> </> );};
const ChildB = () => { return ( <> This is ChildB Component. <br /> <ChildC /> </> );};
const ChildC = () => { const { fName, lName } = useContext(ExampleContext);
return ( <> This is ChildC component. <br /> <h3> Data from Parent component is as follows:</h3> <h4>{fName}</h4> <h4>{lName}</h4> </> );};
#
Manage the local state and the state of GraphQL- Create an initial state and a reducer for the local state:
the initial local state contains all the necessary variables initialized with default values; the reducer is a function that will update part or all of the state content. Use the 'useReducer' hook inside your Provider and destructure the 'state' object and 'dispatch' function. Returns the variables and functions that will be used with the context. - Create a context to use for your pourpose:
the initial state of context must contain the variables from local state
and the variables from GraphQL state, with a defalt value.
The Provider will return all the value to be used in the context
#
Step 1//Initial local state.export const INITIAL_LOCAL_STATE: any = { // replace any with a valid type loadingLanguage: false, language: "IT",};
// Reducer function. Replace any with a valid typeconst reducer = (state: any, updates: Partial<any>): any => { return { ...state, ...updates };};
#
Step 2// the initial state of contextconst profileContextState: any = { // add the dispatch function and all the content of initial local state dispatch: () => {}, ...INITIAL_LOCAL_STATE, // variables from GraphQL state, with a defalt value. projects: [], loadingInProgressProjects: false, loadingOnDeleteProject: false, isAdmin: false}
// create contextexport const ProfileContext = createContext(profileContextState);
//mutation and query examplesconst QUERY_EXAMPLE = gql`query ...`;const MUTATION_EXAMPLE = gql`mutation...`;
// create providerexport const ProfileProvider = (props: any) => { const [state, dispatch] = useReducer(reducer, INITIAL_LOCAL_STATE); const onError = (error: ApolloError) => console.error(error);
//query
const { loading: loadingInProgressProjects, data: { getLoggedUser: { activities: { exploreActivity: { isAdmin = false, projects = [] } = {} } = {} } = {} } = {}, // replace any with a valid type refetch: refetchInProgressProjects} = useQuery<any, any>(QUERY_EXAMPLE, { fetchPolicy: 'no-cache', pollInterval: 30000, onError, } );
// mutation
const [ deleteProject, { loading: loadingOnDeleteProject } // replace any with a valid type ] = useMutation<any, any>(MUTATION_EXAMPLE, { onCompleted: () => { // update all projects with the latest data refetchInProgressProjects(); }, onError } );
const value = { // pass the content of the local state ant the dispatch function ...state, dispatch, // pass all you need to use with the context deleteProject, projects, loadingInProgressProjects, loadingOnDeleteProject, isAdmin };
return ( <ProfileContext.Provider value={value} {...props}> <YourComponent1 /> {/_ <YourComponent2 /> _/} {/_ <YourComponent...n /> _/} </ProfileContext.Provider> );};
// Hook to use for retrieving data from contextexport const useContextValue = () => useContext(ProfileContext);
// How to use variables and functions from contextconst YourComponent1 = () => {// take all you need from contextconst { dispatch, //the reducer function loadingLanguage, // from GraphQL stat language, // from local state deleteProject, // from GraphQL projects // from GraphQL state} = useContextValue();
const handleClick = () => {
// Update the 'loadingLanguage' field of local state
// The dispatch function must be called with an object containing all the // fields to be updated and the relative values to be updated.
dispatch({ // pay atention to use only the local state variables loadingLanguage: true });
// update the 'language' from local state dispatch({ language: language === 'IT' ? 'EN' : 'IT' })
// update again the 'loadingLanguage' field from local state dispatch({ loadingLanguage: false });
};
return loadingLanguage ? (
<div>Loading ...</div>) : ( <div> {projects.map(() => {})} <button onClick={handleClick}> Change language </button>
<button onClick={() => deleteProject()}> Deleted project </button> </div> );}