javascriptreactjsreact-hooksreact-router-domreact-context

React Context resets after changing routing using useNavigate


I am having an issue where my react context data resets whenever I change routes using React-Router-DOM v6 as my routing library. I'm very confused because while I understand manually entering a new URL would cause a reload, I am utilizing useNavigate to change routes, yet none of my data persists.

I have been at this for a while now but no luck solving the issue.

Here is a breakdown of my code.

First, I created an AuthProvider, AuthContext, and a useAuth hook to handle the global auth context.

const authReducer = (state: InitialState, action: Actions) => {
  switch (action.type) {
    case ActionType.SET_AUTH:
      return { ...state, ...action.payload };
    case ActionType.REMOVE_AUTH:
      return {};
    default:
      return state;
  }
};

export const useAuth = () => {
  const [state, dispatch] = useReducer(authReducer, {});

  const setAuth = useCallback(
    (data: InitialState) => {
      dispatch({ type: ActionType.SET_AUTH, payload: data });
    },
    [dispatch]
  );

  const removeAuth = useCallback(() => {
    dispatch({ type: ActionType.REMOVE_AUTH, payload: {} });
  }, [dispatch]);

  return { auth: state, setAuth, removeAuth };
};

const initialAuthContext: UseAuthContext = {
  auth: {},
  setAuth: () => {},
  removeAuth: () => {},
};

export const AuthContext = createContext<UseAuthContext>(initialAuthContext);

export const AuthProvider = ({ children }: AuthContextChildren) => {
  return (
    <AuthContext.Provider value={useAuth()}>{children}</AuthContext.Provider>
  );
};

Next, I added the AuthProvider to my App.tsx.

function App() {
  ...
  return (
    <Box>
      <ThemeProvider theme={theme}>
        <AuthProvider>
          <BrowserRouter>
            <Router />
          </BrowserRouter>
        </AuthProvider>
      </ThemeProvider>
    </Box>
  );
}

Inside my <Router /> I aded the respective routes for Dashboard and Project.

const Router = () => {
  ...
  return (
    <Box sx={{ display: "flex" }}>
      ...
      <Routes>
        ...
        <Route
          path={SectionRoutes.Dashboard} // /dashboard
          element={<Dashboard updateCurrentSection={updateCurrentSection} />}
        />
        <Route
          path={SectionRoutes.Project} // /project
          element={<Project updateCurrentSection={updateCurrentSection} />}
        />
      </Routes>
    </Box>
  );
};

Inside <Dashboard /> I added an accessToken to my auth context.

const Dashboard = () => {
  ...

  const { auth, setAuth } = useAuth();
  useEffect(() => setAuth({ accessToken: "" }), []); // UPDATING CONTEXT
  console.log(auth);

  return (
    ...
  );
};

Utilizing my apps SideNav I navigated to <Project /> using const navigate = useNavigate().

<ListItemButton
   onClick={() => navigate(item.path)} // using useNavigate from 'react-router-dom'
   selected={isSelected}
>

I expect to see my auth context have a key value pairing for accessToken: "" but I get {}.

const Project = ( => {
  ...

  const { auth, setAuth } = useAuth();
  console.log(auth); // CONTEXT IS EMPTY

  return (
    ...
  );
};

export default Project;

I tried a multitude of things such as changing how the context was written, seeing if I made an error with the routing, and searching Stack Overflow, but all the answer left me wanting.


Solution

  • Each useAuth hook is managing its own state and callbacks.

    Move the logic into the AuthProvider component so the single Context value can be provided to all consumers, update the useAuth hook to return the context value.

    const authReducer = (state: InitialState, action: Actions) => {
      switch (action.type) {
        case ActionType.SET_AUTH:
          return { ...state, ...action.payload };
        case ActionType.REMOVE_AUTH:
          return {};
        default:
          return state;
      }
    };
    
    const initialAuthContext: UseAuthContext = {
      auth: {},
      setAuth: () => {},
      removeAuth: () => {},
    };
    
    export const AuthContext = createContext<UseAuthContext>(initialAuthContext);
    
    export const useAuth = () => useContext(AuthContext);
    
    export const AuthProvider = ({ children }: AuthContextChildren) => {
      const [state, dispatch] = useReducer(authReducer, {});
    
      const setAuth = useCallback((data: InitialState) => {
        dispatch({ type: ActionType.SET_AUTH, payload: data });
      }, [dispatch]);
    
      const removeAuth = useCallback(() => {
        dispatch({ type: ActionType.REMOVE_AUTH, payload: {} });
      }, [dispatch]);
    
      return (
        <AuthContext.Provider value={{ auth: state, setAuth, removeAuth }}>
          {children}
        </AuthContext.Provider>
      );
    };