DEV Community

Discussion on: Protected Routes with React Function Components

Collapse
 
johnmarsden24 profile image
Jonny • Edited

@franciscobenedict I had this exact same issue. Firebase will persist an authenticated users details in local storage, however to retrieve them initially is asynchronous which as you've experienced doesn't work when refreshing the page and you get taken to a login screen, etc.

As we will be wanting to know about this user state across multiple components its advised to wrap all this logic into a context provider. To start solving this we can utilise onAuthStateChanged from Firebase which is the recommend way of retrieving a user as it allows Firebase to initialise, it will provide either the user object or null in the callback. What we also need is a loading state, which is true on initial launch and when the user has been loaded or if there isn't one we can stop loading. To get this behaviour we can wrap onAuthStateChanged in a promise which resolves the user and call it on initial launch using useEffect. We can then wait for this promise to settle before we manage our final loading state. Finally we can decide what to do once we've finished loading and we either have or don't have a user.

Here's my code:

Helper function

const getCurrentUser = () =>
  new Promise((res, rej) => {
    functionsApp.auth().onAuthStateChanged((user) => res(user));
  });
Enter fullscreen mode Exit fullscreen mode

AuthContext.js

const AuthContext = createContext({
  user: null,
  loadingUser: true,
  setCurrentUser: () => {},
  unsetCurrentUser: () => {},
});

export const AuthContextProvider = (props) => {
  const [user, setUser] = useState(null);
  const [loadingUser, setLoadingUser] = useState(true);

  useEffect(() => {
    getCurrentUser().then((user) => {
      setUser(user);
      setLoadingUser(false);
    });
  }, []);

  const setCurrentUser = (user) => setUser(user);
  const unsetCurrentUser = () => signOutUser().then(() => setUser(null));

  const contextValue = {
    user,
    loadingUser,
    setCurrentUser,
    unsetCurrentUser,
  };

  return (
    <AuthContext.Provider value={contextValue}>
      {props.children}
    </AuthContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

ProtectedRoute.js

function ProtectedRoute({ component: Component, ...restOfProps }) {
  const { user, loadingUser } = useContext(AuthContext);

  if (loadingUser) {
    return <p>Loading..</p>;
  }

  return (
    <Route
      {...restOfProps}
      render={(props) =>
        user ? <Component {...props} /> : <Redirect to="/auth" />
      }
    />
  );
}
Enter fullscreen mode Exit fullscreen mode