0

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});
Boardy
  • 35,417
  • 104
  • 256
  • 447
  • Where do you set the value of `global_state`? If you never set it in LocalStorage, then you can't expect it to return anything on reload. – Matt Morgan Oct 26 '21 at 12:04
  • I've obviously checked that the local storage has the value set which it does in the chrome dev tools. But I've added an example of storing it in the local storage to the question. – Boardy Oct 26 '21 at 13:54
  • Hmm...maybe your localstorage is in a bad state. I was able to get your code working with a simplified version of globalState: https://codesandbox.io/s/stoic-pine-rnrb8 Your basic idea is sound. Might help troubleshooting to simplify as much as possible... – Matt Morgan Oct 26 '21 at 14:49
  • Weird, the codesandbox has somehow become corrupted. If it's useful I cna try to restore it. LMK. – Matt Morgan Oct 26 '21 at 16:24
  • I've managed to get it working. It seems like the useEffect wasn't being triggered, I've implemented it before the return of the component and check the state is not already set and if not then pull from localstorage and set the global storage. Thanks for your help though – Boardy Oct 26 '21 at 17:24
  • I'm not sure what that means exactly...your code above looks like it's doing that already? Glad it's working though! – Matt Morgan Oct 26 '21 at 19:57
  • Sorry didn't explain that very well, in the OP I was using the useEffect, the code from in the useEffect I moved out so it always runs before the return function instead of in the useEffect. Then this just check if the global state is not already set, and if not, pull it from local storage and then use the value from local storage to set the global state. – Boardy Oct 27 '21 at 12:50

0 Answers0