0

I'm trying to implement protected pages with Firebase authentication. I created a typical PrivateRoute component that is supposed to only show the page if they're logged in, or redirect users to a login page if they aren't logged in.

I stored the authentication status in a global state using a useEffect hook in App.js. After a lot of reading and research, I understand that the useEffect only completes after the Child component has loaded.

Having said that, I'm at a loss on how to pass authenticated from App to PrivateRoute. With my current code, the authenticated state only registers as true after PrivateRoute has pushed users to the login page. Would appreciate any help.

App.js

//context
 const [{user, authenticated}, dispatch] = useStateValue();

  useEffect(() => {
    auth.onAuthStateChanged((authUser) => {
      console.log("THE USER IS >>> ", authUser);

      if (authUser) {
        dispatch({
          type: "SET_USER",
          user: authUser,
        });
        dispatch({
          type: "SET_AUTH",
          authenticated: true,
        })

      } else {
        // the user is logged out
        dispatch({
          type: "SET_USER",
          user: null,
        });
        dispatch({
          type: "SET_AUTH",
          authenticated: false,
        })
      }
    });
  }, []);

return (
    <Router>
      <div className="app">
          <PrivateRoute exact isAuth={authenticated} path="/account/create-store" component={CreateAccount} />
      </div>
    </Router>
)

PrivateRoute.js

import { Route, Redirect } from 'react-router';
import { useStateValue } from './StateProvider';

function PrivateRoute ({ isAuth: isAuth, component: Component, ...rest }) {
    const [{authenticated}, dispatch] = useStateValue();

    return (
        <Route {...rest} render={(props) => {
            if (isAuth)  {
                return <Component />
            } else {
                return (
                <Redirect to={{ pathname:"/login", state: {from: props.location }}} />
                );
            }
          }} />
    )
}

export default PrivateRoute

reducer.js

export const initialState = {
    user: null,
    authenticated: false,
};

const reducer = (state, action) => {
    console.log(action)
    switch(action.type) {
        case "SET_USER":
            return {
                ...state,
                user: action.user,
            }

        case "SET_AUTH":
            return {
                ...state,
                authenticated: action.authenticated,
            }


        default:
            return state;
}}

export default reducer;
1
  • This isn't really an answer, but I found this post (stackoverflow.com/questions/58769189/…) and practically speaking it gets things working. I would still greatly appreciate an explanation as to why my approach was flawed though. Commented Mar 27, 2021 at 13:45

1 Answer 1

2

I can't reproduce your exact problem but I think your PrivateRoute is wrong. Try something like the example bellow.

function PrivateRoute({ isAuth, component: Component, ...rest }) {
  if (!isAuth) {
    return <Redirect to={{ pathname:"/login", state: {from: props.location }}} />;
  }

  return <Route component={Component} {...rest} />;
}

export default PrivateRoute;

Use a isLoading state variable so you are not redirected before checking the firebase.auth().onAuthStateChanged.

const [{user, authenticated}, dispatch] = useStateValue();
const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    auth.onAuthStateChanged((authUser) => {
      console.log("THE USER IS >>> ", authUser);

      if (authUser) {
        dispatch({
          type: "SET_USER",
          user: authUser,
        });
        dispatch({
          type: "SET_AUTH",
          authenticated: true,
        })

      } else {
        // the user is logged out
        dispatch({
          type: "SET_USER",
          user: null,
        });
        dispatch({
          type: "SET_AUTH",
          authenticated: false,
        })
      }
      setIsLoading(false);
    });
  }, []);

if(isLoading) {
  return <div>Loading...</div>
}

return (
    <Router>
      <div className="app">
          <PrivateRoute exact isAuth={authenticated} path="/account/create-store" component={CreateAccount} />
      </div>
    </Router>
)
Sign up to request clarification or add additional context in comments.

2 Comments

In this case, it Fails to compile because props is no longer an argument and now undefined. I added my reducer file to the post, though I don't believe it's the cause of the problem. I have tried making authenticated a local state where I just manually switch true and false and that seems to work. But when I use firebase.auth().onAuthStateChanged in the hook, it only updates the context state after checking PrivateRoute, so it redirects me to login every time because authenticated=false.
I have updated the answer and now you will not be redirected before checking before the firebase.auth().onAuthStateChanged runs. And yes, you need to remove the prop inside the Redirect. if you want to Redirect the user after the login you can use something like the useHistory hook provided by react-router-dom.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.