skip to Main Content

I have an application where people can access to the website if not login, however they should be able to access to "/app", "/app/*" only if authenticated. My code below works but for some reason there is half a second, or a second of the content of "/app" shown before showing the message "you're not allowed to...". Any idea why is that?

import { Provider, useDispatch, useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import '../styles/front.css';
import '../styles/app.css';
import React, { useEffect, useState } from 'react';
import { wrapper, store } from '../store';
import { login, logout, selectUser } from "../redux/slices/userSlice";
import { auth } from '../firebase';

function MyApp({ Component, pageProps }) {
  const user = useSelector(selectUser);
  const dispatch = useDispatch();
  const router = useRouter();
  const isAppPage = router.pathname.startsWith('/app');
  const [shouldRender, setShouldRender] = useState(true); // New state variable
  const [loading, setLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((userAuth) => {
      if (userAuth) {
        dispatch(login({
          email: userAuth.email,
          uid: userAuth.uid,
          displayName: userAuth.displayName,
          photoUrl: userAuth.photoURL
        }));
        setIsAuthenticated(true);
      } else {
        if (isAppPage) {
          setShouldRender(false);
        }
        dispatch(logout());
        setIsAuthenticated(false);
      }
      setLoading(false); // Set loading to false once the authentication status is checked
    });

    return () => unsubscribe(); // Cleanup the event listener when the component unmounts
  }, []);

  return (
    <Provider store={store}>
      {shouldRender ? <Component {...pageProps} /> : <p>You're not allowed to access that page.</p>}
    </Provider>
  );
}

export default wrapper.withRedux(MyApp);

2

Answers


  1. That is because shouldRender is true on first load, and after that it goes inside useEffect and get auth state inside onAuthStateChanged.

    you should set it to false here:

    const [shouldRender, setShouldRender] = useState(false); // New state variable
    

    and update inside it:

     if (userAuth) {
        dispatch(login({
          email: userAuth.email,
          uid: userAuth.uid,
          displayName: userAuth.displayName,
          photoUrl: userAuth.photoURL
        }));
        setShouldRender(true);
        setIsAuthenticated(true);
      }
    

    Update:
    You can use another state to check loaded state.
    so use:

    const [loaded, setLoaded] = useState(false);    
    const [shouldRender, setShouldRender] = useState(false);
    

    and update inside useEffect:

      if (userAuth) {
        dispatch(login({
          email: userAuth.email,
          uid: userAuth.uid,
          displayName: userAuth.displayName,
          photoUrl: userAuth.photoURL
        }));
        setLoaded(true);
        setShouldRender(true);
        setIsAuthenticated(true);
      }
    

    For render use this:

     return (
        <Provider store={store}>
          {!loaded? <LoaderComponent />: shouldRender
            ? <Component {...pageProps} />
            : <p>You're not allowed to access that page.</p>
          }
        </Provider>
      );
    
    Login or Signup to reply.
  2. You basically need a condition with a third value that is neither "show the content" nor "you can’t see this content". Something like a "pending" state that conditionally renders neither the Component nor the "You're not allowed to access that page." text.

    The initial shouldRender state matches one of these two states you don’t want to immediately render. Start with undefined and explicitly check for this and conditionally return null or a loading indicator, etc.
    Example:

    function MyApp({ Component, pageProps }) {
      const user = useSelector(selectUser);
      const dispatch = useDispatch();
      const router = useRouter();
      const isAppPage = router.pathname.startsWith('/app');
    
      const [shouldRender, setShouldRender] = useState(); // initially undefined
    
      const [loading, setLoading] = useState(true);
      const [isAuthenticated, setIsAuthenticated] = useState(false);
    
      useEffect(() => {
        const unsubscribe = auth.onAuthStateChanged((userAuth) => {
          if (userAuth) {
            dispatch(login({
              email: userAuth.email,
              uid: userAuth.uid,
              displayName: userAuth.displayName,
              photoUrl: userAuth.photoURL
            }));
            setIsAuthenticated(true);
            setShouldRender(true); // <-- show the content
          } else {
            if (isAppPage) {
              setShouldRender(false); // <-- hide content
            }
            dispatch(logout());
            setIsAuthenticated(false);
          }
          setLoading(false);
        });
    
        return () => unsubscribe();
      }, []);
    
      if (shouldRender === undefined) {
        return null; // or loading indicator/spinner/etc
      }
    
      return (
        <Provider store={store}>
          {shouldRender
            ? <Component {...pageProps} />
            : <p>You're not allowed to access that page.</p>
          }
        </Provider>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search