javascriptreactjsreact-redux

useSelector first returns undefined, then returns user


// auth provider
import React from "react";
import { useState } from "react";
import { useSelector } from "react-redux";
import { Navigate, useLocation } from "react-router-dom";

export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
  const currentUser = useSelector((state) => state.userData.user);
  const location = useLocation();

  if (!currentUser)
    return (
      <Navigate to="/signIn" replace state={{ path: location.pathname }} />
    );

  return (
    <AuthContext.Provider value={currentUser}>{children}</AuthContext.Provider>
  );
};
//main.jsx
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
      <Provider store={store}>
        <BrowserRouter>
          <Routes>
            <Route path='/signIn' element={<GoogleSignIn />} />
            <Route path='*' element={<App />}/>
          </Routes>
        </BrowserRouter>
      </Provider>
  </React.StrictMode>
)
// app.jsx
<div className="App grid">
  <AuthProvider>
     <Navbar />
     <Routes>
       <Route path="channels" element={<UserChannels />} />
       <Route path="/channel/new" element={<CreateChannel />} />
       <Route path="/channel/:channelID" element={<Channel />} />
     </Routes>
  </AuthProvider>
</div>

I want to have protected routes so, only when user is logged in they can go to these routes else they must be redirected to signIn page

Storing user info as redux state

useSelector returns null immediately so the page navigates to signIn page


Solution

  • For the first time rendering, the useSelector will always return a default value of userData.user, then you can fill it with user data that you get from API call in your app. You might need to add isLoadingUser in your store.

    export const AuthProvider = ({ children }) => {
      const {user: currentUser, isLoadingUser} = useSelector((state) => state.userData);
      const location = useLocation();
    
      if(isLoadingUser){
        return <LoadingPage/>
      }
    
      if (!currentUser && !isLoadingUser)
        return (
          <Navigate to="/signIn" replace state={{ path: location.pathname }} />
        );
    
      return (
        <AuthContext.Provider value={currentUser}>{children}</AuthContext.Provider>
      );
    };