Search code examples
javascriptreactjsreact-contextsession-storage

React Complex Context saved to sessionStorage


I am currently storing user information within a context object within my React app.

export const SessionContext = createContext(null);

export const SessionContextProvider = ({ children }) => {
  console.debug("RTS Break SessionContextProvider");

  const [user, setUser] = useState(null);
  const [userAuthorized, setUserAuthorized] = useState(false);
  const [userAuthToken, setUserAuthToken] = useState(null);
  const [sessionCustomer, setSessionCustomer] = useState(null);
  const [prevSessionCustomer, setPrevSessionCustomer] = useState(null); 
  const [appMode, setAppMode] = useState(null);
  const apiRoot = process.env.REACT_APP_API_URL;


  const userLogOut = () => {
    setUserAuthToken("");
    setUser(null);
    setUserAuthorized(false);
    setSessionCustomer(null);
    setPrevSessionCustomer(null);
  }

  const value = {
    user,
    setUser,
    userAuthorized,
    setUserAuthorized,
    userAuthToken,
    setUserAuthToken,
    sessionCustomer,
    setSessionCustomer,
    prevSessionCustomer,
    setPrevSessionCustomer,
    appMode,
    setAppMode,
    apiRoot,
    userLogOut
  };

  return(
      <SessionContext.Provider value={value}> {children} </SessionContext.Provider>
  );

};

export const useSessionContext = () => React.useContext(SessionContext);

As you can see I am storing a number of different things including child objects within this Context object. In order for all objects to have access to this data I have wrapped my main window within the provider within App.js.

export default function App(props) {
  return (
    <ThemeProvider theme={theme}>
      <SessionContextProvider>
        <BrowserRouter>
          <Box id="appBox">
            <Main />
          </Box>
        </BrowserRouter>
      </SessionContextProvider>      
    </ThemeProvider>
  );

Some of the attributes on my context object are set when the user logs in and some are set as they use the product. Here is some of the code in my login control that saves the data to my context object after a user is successfully authenticated:

                if (res.status === 200 && res.data.token) {
                    try{
                        setUserAuthToken({'token': res.data.token});
                    }
                    catch(err){
                        console.log("Token Error");
                        console.log(err);
                    }

                    try{
                        setUser(res.data.user);
                    }
                    catch(err){
                        console.log("User Data Error");
                        console.log(err);
                    }

                    try{
                        setUserAuthorized(true);
                    }
                    catch(err){
                        console.log("Authorization Error");
                        console.log(err);
                    }

I am still somewhat of a noob to React so I have a couple of questions.

  1. Am I using the context wrongly? It seems like I should be able to store an object on context and retrieve/update them individually but am not sure if I have misunderstood what it was intended to do.
  2. Is there a way that I can save all values to sessionStorage or do I have to save and retrieve each one individually?
  3. How do I initialize these values back from sessionStorage when a user hits the refresh button?

Solution

    1. Your use of context looks correct. However if your state management gets more complex, you might consider using a state management library such as Zustand, redux or MobX.
    2. Yes, you can save all the values at once in session storage by using a JSON string. And whenever you want to retrieve the values simply parse it back to an object. Here's an example:
    // Building and saving your sessionObject
    const sessionObject = { user: user, userAuthorized: userAuthorized, ... };
    sessionStorage.setItem("session", JSON.stringify(sessionObject));
    
    /// Retrieving your whole sessionObject
    const storedSession = sessionStorage.getItem("session");
    if (storedSession) {
      const { user, userAuthorized, ... } = JSON.parse(storedSession);
    } else {
      // No session stored
    }
    
    1. I would like you to think about on where exactly to save the elements into session storage. After you've saved the object in session storage you can retrieve the session object from the session storage in SessionContextProvider and use useState to set the initial state of each context value like this:
    export const SessionContextProvider = ({ children }) => {
      const [user, setUser] = useState(null);
      const [userAuthorized, setUserAuthorized] = useState(false);
      const [userAuthToken, setUserAuthToken] = useState(null);
      const [sessionCustomer, setSessionCustomer] = useState(null);
      const [prevSessionCustomer, setPrevSessionCustomer] = useState(null);
      const [appMode, setAppMode] = useState(null);
      const apiRoot = process.env.REACT_APP_API_URL;
    
      useEffect(() => {
        const storedSession = sessionStorage.getItem("session");
        if (storedSession) {
          const session = JSON.parse(storedSession);
          setUser(session.user);
          setUserAuthorized(session.userAuthorized);
          setUserAuthToken(session.userAuthToken);
          setSessionCustomer(session.sessionCustomer);
          setPrevSessionCustomer(session.prevSessionCustomer);
          setAppMode(session.appMode);
        }
      }, []);
    
      const userLogOut = () => {
        setUserAuthToken("");
        setUser(null);
        setUserAuthorized(false);
        setSessionCustomer(null);
        setPrevSessionCustomer(null);
      }
    
      const value = {
        user,
        setUser,
        userAuthorized,
        setUserAuthorized,
        userAuthToken,
        setUserAuthToken,
        sessionCustomer,
        setSessionCustomer,
        prevSessionCustomer,
        setPrevSessionCustomer,
        appMode,
        setAppMode,
        apiRoot,
        userLogOut
      };
    
      return (
        <SessionContext.Provider value={value}>
          {children}
        </SessionContext.Provider>
      );
    };