skip to Main Content

I have a Switch with a series of Route elements in my main container:

AppContainer.js

<Switch>
  <Route exact path={`/app/:slug/`}>
    <Redirect to={`/app/${slug}/home`} />
  </Route>
  <ProtectedRoute
    path='/app/:slug/userAdmin/'
    component={UserAdminContainer}
    message={'This page is read-only'}
  />
  [snip]
  <Route
    path='/app/:slug/protectedRoute/'
    render={(props) => (
      <ReadOnlyError message={props.message} />
    )}
  />
</Switch>

Along with the ProtectedRoute.js

const ProtectedRoute = (props) => {
  const { message } = props;
  const { isReadOnlyUser } = useUser();
    
  return (
    isReadOnlyUser
      ?
        <Redirect
          to={{
            pathname: '/app/:slug/protectedRoute/',
            state: {message}
          }}
        />
      : <Route {...props} />
  )
}

And the useUser function

function useUser() {
  const context = React.useContext(UserContext);
  if (context === undefined) {
    throw new Error(`User data is not provided to this component`);
  }

  return context;
}

My problem is that when AppContainer renders, it is then immediately redirected to the ProtectedRoute of "/admin", when it should only render if that button was hit. I’m a longtime Java dev, brand new to React. I’m sure there’s something about the rendering process I’m not understanding.

2

Answers


  1. I think the issue is that the user isn’t loaded on the first render. This might happen if you need any server (or third party) communication for your user to load. With this assumption, one solution would be to wait until the user is loaded. Effectively, you’d probably want to separate the following scenarios:

    • The user is currently being loaded
    • The user is logged out (or credentials expired, loading was unsuccessful)
    • The user was loaded successfully

    If you prevent hitting the switch until the user is loaded, it should work.

    Login or Signup to reply.
  2. What typically happens is that the initial "auth" condition incorrectly matches either the confirmed authenticated or unauthenticated states. In other words, it’s not simply a binary decision, but that there are actually 3 options: "confirmed authenticated", "confirmed unauthenticated", and "we don’t know yet". Unless you are able to directly determine and initialize the isReadyOnlyUser state into one of the "confirmed" cases, you’ll want to initialize to the "unknown" case using a value that neither indicates a user is authenticated nor unauthenticated, and then explicitly check this condition and conditionally return early and not render the protected content or redirect until auth status is confirmed.

    Example, a "confirmed" isReadOnlyUser is a boolean true|false value, so a great "unknown" initial value would be undefined.

    const [isReadyOnlyUser, setIsReadyOnlyUser] = useState(); // undefined
    
    useEffect(() => {
      // check user is read-only on app/component mounting
      // update isReadOnlyUser
    }, []);
    
    ...
    
    return (
      <UserContext value={{ isReadyOnlyUser, /* ...other context values ... */}}>
        {children}
      </UserContext>
    );
    
    import { Route, Redirect, useParams, generatePath } from 'react-router-dom';
    
    const ProtectedRoute = ({ message, ...props }) => {
      const { slug } = useParams();
      const { isReadOnlyUser } = useUser();
    
      if (isReadyOnlyUser === undefined) {
        return null; // or loading indicator/spinner/etc
      }
        
      return isReadOnlyUser
        ? (
          <Redirect
            to={{
              pathname: generatePath("/app/:slug/protectedRoute/", { slug }),
              state: { message }
            }}
          />
        ) : <Route {...props} />
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search