Skip to main content

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 context#

Prop 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 example#

Data 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#

  1. 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.
  2. 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>    );}