2

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);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Brian Millot
  • 179
  • 1
  • 12

2 Answers2

0

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>
  );
Komal
  • 1,068
  • 12
  • 23
  • This just inverts the problem. Instead of unauthenticated users seeing protected content prior to the "You're not allowed..." text, authenticated users will see the "You're not allowed..." text for a brief moment before seeing the protected content. – Drew Reese May 18 '23 at 07:53
  • I have updated my answer. I hope it makes sense now. – Komal May 18 '23 at 08:11
0

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>
  );
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks, if not logout i cannot see the content of the website anymore, if i go to /app correct behaviour showing im not allowed – Brian Millot May 18 '23 at 07:55
  • because users still need to be able to access the website, only if login they can access to /app/etc however – Brian Millot May 18 '23 at 07:56
  • @BrianMillot Are these routes you are rendering somewhere in your app? If so then you likely need to implement [protected routes](https://stackoverflow.com/a/66289280/8690857) instead of conditionally rendering all or nothing here in the root app component. Same idea as this answer, but applied at the routing level. Does this make sense? – Drew Reese May 18 '23 at 07:58
  • Thanks mate, nope but just by doing if (isAppPage) { setShouldRender(false); // <-- hide content } else { setShouldRender(true); } seems to have fixed the issue for now thanks! – Brian Millot May 18 '23 at 08:01