2

I have the following PrivateRoute.js code:

export const PrivateRoute = ({ component: Component, roles, ...rest }) => {
    const { user } = useContext(AuthContext);
    // currentUser = {something: value, something: value, role: "admin" }
    const { currentUser } = user;
    
    return (
        <Route {...rest} render={props => {
            if (!currentUser) {
                return <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
            }

        if (roles && roles.indexOf(currentUser.role) === -1) {
            return <Redirect to={{ pathname: '/'}} />
        }

        // authorised so return component
        return <Component {...props} />
    }} />
  )
}

And this is my app.js file for routing:

<div className="col-md-12">
    <PrivateRoute exact path="/" component={HomePage} />
    <PrivateRoute path="/page1" roles={[Role.User, Role.Admin]} component={Page1} />
    <PrivateRoute path="/page2" roles={[Role.User, Role.Admin]} component={Page2} />
    <PrivateRoute path="/page3" roles={[Role.Admin]} component={Page3} />
</div>

UPDATE: AuthContext.js

const parseUser = (x) => ({
    currentUser: x,
    isAdmin: x && x.role === Role.Admin,
    username: localStorage.getItem('email')
});

export const AuthContext = React.createContext({});

export const AuthContextProvider = ({ children }) => {
    const history = useHistory();
    const [user, setUser] = useState({
        currentUser: null,
        isAdmin: false
    });

    const authContext = {
        user,
        setUserFromStorage: () => {
            const userStorage = JSON.parse(localStorage.getItem('loggedUser'));
            setUser(parseUser(userStorage));
        }
    },
    login: (email, password) => {
        return fetch('calltoAPI')
        .fetch(response => {
            setUser(parseUser(response));
            localStorage.setItem('loggedUser', response);
            return response;
        })
    }
     
    return (
        <AuthContext.Provider value={authContext}>
            {children}
        </AuthContext.Provider>
    );
}

This works fine if I'm navigating from the app (with <NavLink to="path" href="path", etc.) the problem is that when I'm trying to access page1, 2 or 3 typing the path directly on the browser url the app redirects me to "/". I write a console.log into PrivateRoute before the if (roles) statement and when I type the url manually is empty.

Why I'm getting this behaviour or what can I do to solve it?

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
User1899289003
  • 850
  • 2
  • 21
  • 40
  • If you are directing to `"/'` then obviously the `roles` prop is there, I don't see any way for it ***not*** to be, and the overall condition is evaluating true, I don't see any way for it ***not*** to be. I think the issue then is with the `currentUser` object. The `currentUser.role` ***must*** not be in the `roles` array for the condition to evaluate true and render the `Redirect` to the `"/"` path. This issue is more to do with your `AuthContext` than it does routing/navigation. Can you update your question to share the `AuthContext` code and what this `user` object's value could be? – Drew Reese Dec 09 '21 at 23:01
  • @DrewReese Hello! Thanks for your comment. I added my AuthContext code. – User1899289003 Dec 09 '21 at 23:40

1 Answers1

1

From what I can tell you don't initialize the user state from the local storage when initializing the AuthContext, especially after a page reload/refresh. Create a state initialization function to read from localStorage and provide the initial state value.

const defaultState = {
  currentUser: null,
  isAdmin: false
};

const parseUser = (x) => ({
  currentUser: x,
  isAdmin: x?.role === Role.Admin,
  username: localStorage.getItem('email')
});

/**
 * Lazy state initialization
 * Read from localStorage and return parsed stored user
 * or default initial state object
 */
const initializeFromStorage = () => {
  return parseUser(JSON.parse(localStorage.getItem('loggedUser')) || defaultState);
};

export const AuthContextProvider = ({ children }) => {
  const history = useHistory();

  // Lazy initialize state
  const [user, setUser] = useState(initializeFromStorage());

  const authContext = {
    user,
    setUserFromStorage: () => {
      const userStorage = JSON.parse(localStorage.getItem('loggedUser'));
      setUser(parseUser(userStorage));
    },
    login: (email, password) => {
      return fetch('calltoAPI')
        .then(response => {
          setUser(parseUser(response));
          localStorage.setItem('loggedUser', response);
          return response;
        });
      };
    },
  };
     
  return (
    <AuthContext.Provider value={authContext}>
      {children}
    </AuthContext.Provider>
  );
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181