I am working on a project where I have implemented global state using what's available within ReactJS, no third party library is being used.
When I set the global state, I then also add it to the local storage so if the browser reloads, the idea is that I'll be able to pull the data out of local storage and set it to the global state but this doesn't appear to be working as I keep getting back an undefined error when trying to access a property within the global state after page reload.
My GlobalStateProvider is as follows:
import React, {createContext, useState, useContext, Dispatch, SetStateAction, useEffect} from "react";
export interface GlobalStateInterface {
trial: {
in_trial: boolean,
days_of_trial_remaining: number
}
payment_failed: {
last_payment_failed: boolean,
payment_time_required: number,
last_payment_failed_reason: string,
payment_required: boolean
}
}
const GlobalStateContext = createContext({
globalState: {} as Partial<GlobalStateInterface>,
setGlobalState: {} as Dispatch<SetStateAction<Partial<GlobalStateInterface>>>,
});
const GlobalStateProvider = ({
children,
value = {} as GlobalStateInterface,
}: {
children: React.ReactNode;
value?: Partial<GlobalStateInterface>;
}) => {
const [globalState, setGlobalState] = useState(value);
return (
<GlobalStateContext.Provider value={{ globalState, setGlobalState }}>
{children}
</GlobalStateContext.Provider>
);
};
const useGlobalState = () => {
const context = useContext(GlobalStateContext);
if (!context) {
throw new Error("useGlobalState must be used within a GlobalStateContext");
}
return context;
};
export { GlobalStateProvider, useGlobalState };
I have created a BaseComponent which I was expecting I could use to check if the localStorage contains the global state provider and if so load it into the global state, so this component contains the following:
import * as React from "react";
import {useEffect} from "react";
import {useGlobalState} from "./GlobalStateProvider";
export default function BaseComponent(props) {
const { setGlobalState } = useGlobalState();
useEffect(() => {
let tempGlobalStorage = localStorage.getItem("global_state");
console.log("Temp Global State: ", tempGlobalStorage);
if (tempGlobalStorage !== null) {
const tempGlobalStorageJson = JSON.parse(tempGlobalStorage);
setGlobalState({...tempGlobalStorageJson});
}
}, []);
return (
<>
{props.children}
</>
)
}
My App.js looks like the following: (I'm only showing what is being returned in App.js as I don't think anything is relevant)
return (
<Elements stripe={stripePromise}>
<GlobalStateProvider>
<BaseComponent>
<BrowserRouter>
<div className='h-full'>
<Switch>
<Route path="/" render={(props) => <Login {...props} /> } exact />
<Route path="/login/:msg?/:redirect?" render={(props) => <Login {...props} /> } exact />
<Route path="/choose-plan" render={() => <ChoosePlan /> } exact />
<Route path="/signup/:plan" render={(props) => <SignUp {...props} /> } exact />
<Route path="/dashboard" render={() => <Dashboard /> } exact />
<Route path="/project-settings/:project_id" render={(props) => <ProjectSettings {...props} /> } />
<Route path="/settings" render={() => <Settings /> } exact />
<Route path='/add-project' render={() => <AddProject /> } exact />
<Route path='/final-project-setup/:platform/:project_id/:api_key' render={(props) => <FinalProjectSetup {...props} /> } />
<Route path='/crash-details/:project_id/:project_type/:crash_group_id?/:comment_id?' render={(props) => <CrashDetails {...props} /> } />
<Route path='/team-members' render={() => <ManageTeamMembers /> } />
<Route path='/add-team-member' render={() => <AddTeamMember /> } />
<Route path='/register-new-user/:invite_params?' render={(props) => <RegisterNewUser {...props} /> } />
<Route path='/account-billing' render={() => <AccountAndBilling /> } exact />
<Route path='/forgotten-password' render={() => <ForgottenPassword /> } exact />
<Route path='/reset-password/:resetPasswordData' render={(props) => <ResetPassword {...props} /> } />
<Route path='/status' render={() => <Status /> } />
<Route render={() => <NotFound />}/>
</Switch>
</div>
</BrowserRouter>
</BaseComponent>
</GlobalStateProvider>
</Elements>
);
As you can see in the BaseComponent I log out what is in the localStorage it doesn't output anything on page reload, its as if where I am accessing the global state in one of the route components is getting triggered before the BaseComponent triggers the useEffect.
An example of where the state and global storage is set is below:
const tempGlobalState = globalState;
tempGlobalState.trial = {
in_trial: response.data.in_trial,
days_of_trial_remaining: response.data.in_trial ? response.data.trial_days_remaining : null
}
tempGlobalState.payment_failed = {
last_payment_failed: response.data.last_payment_failed,
last_payment_failed_reason: response.data.last_payment_failed_reason,
payment_time_required: response.data.payment_time_required,
payment_required: false //Still within the 7 day grace period for non payment
}
/*tempGlobalState.trial.in_trial = response.data.in_trial;
if (response.data.in_trial)
{
tempGlobalState.trial.days_of_trial_remaining = response.data.trial_days_remaining;
}*/
localStorage.setItem("global_state", JSON.stringify(tempGlobalState));
setGlobalState({...tempGlobalState});