0

I am building a react app to learn react. I have implemented context to store information about the user when they login and also am making a copy in the localstorage. I verify that users are logged in like this in the App component

function App() {
const [user, setUser] = useContext(UserContext);
return (
        <div className="App">
            <Router>
                <NavBar />
                    <Switch>
                        <Route path="/login" exact>
                            {!user.loggedIn ? <LoginForm /> : <Redirect to="/profile" />}
                        </Route>
                    </Switch>
            </Router>
        </div>
    );

And this works fairly well. But if the token has expired; I want the user to be forced into the login page. Which means I need to check the expiration time on every route request as users can be in there profile and if they click on another link they will be redirected to login component. How should I approach this? I have tried to to use useEffect in app component that sets the state loggedIn to true true when it meets the condition and made a comparison like

    useEffect(function () {
        const ct = parseInt(Date.now().toString(), 10);
        const iat = parseInt(localStorage.getItem('iat'), 10);
        if (ct - iat < 60000) {
            setUser({ loggedIn: true });
        } else {
            setUser({ loggedIn: false });
        }
    });

From my understanding this should happen everytime App component is rendered hence I provide no input to useEffect but this obviously throws a maximum update depth reached error. How should I implement this behaviour?

sakib11
  • 376
  • 3
  • 13

2 Answers2

1

Edit: App component won't rerender because it's never changing and only the subcomponents are. What you can do is, you can put history.location as your dependency in the useEffect, which will trigger the useEffect whenever the path changes. See: How to listen to route changes in react router v4?

If you console.log in your component body, you'll see that it rerenders a lot of times with every node updating. You need to add either an empty dependency array or some dependencies so it only runs the useEffect when it's needed.

useEffect(() => {
//your logic here
}, [])

I think you can also mitigate this error by having another check on your setUser method. This way you'll mitigate a lot of unnecessary calls to the setState method which is one of the reasons for this maximum depth reached error

useEffect(function () {
       //other code
        if (!loggedIn && ct - iat < 60000) {
            setUser({ loggedIn: true });
        } else if(loggedIn){
            setUser({ loggedIn: false });
        }
    });

Furthermore, there are usually better ways to check whether the user needs to reauthenticate, if you are using Axios, you can set up a check in the interceptor to do this comparison before making a call and if the user is unauthed, redirect them to login page.

Danyal
  • 697
  • 3
  • 11
  • Use Effect is needed everytime a route operation is perfomed. I am not sure how to add that as a dependency. And for the second solution; I have tried it before and it did not work. For axios; I still have not used it since I am only trying learn the basics of react. – sakib11 Apr 06 '20 at 14:23
  • 1
    Is there a `focused` parameter in `react-router` which you can use to trigger the useEffect every time a route operation is performed because the components will change. Other than that, useEffect with an empty array dependency array should still trigger every time a component mounts and dismounts due to route change – Danyal Apr 06 '20 at 14:38
  • No there isn't. Should I take any other approaches ? – sakib11 Apr 06 '20 at 15:00
  • 1
    If you really want to do it this way, then consider making Private and Public routes separate and check for authentication when the Private route is hit. https://reacttraining.com/react-router/web/example/auth-workflow Just to reiterate, I have previously used useEffect with just an empty array dependency to do this and it has worked because it's called every time the component mounts and unmounts. Not sure how you would do it when you are already on the route without redux or axios – Danyal Apr 06 '20 at 15:49
  • Also I tried to see if i use useEffect with an empty array to see if it renders on a link change and It does not. I think its because all the components are wrapped with app component hence everything else may re render but not the app component. – sakib11 Apr 06 '20 at 15:49
  • 1
    Okay, now I understand it a bit better. You're right, the app component won't rerender because it's never changing and only the subcomponents are. What you can do is, you can put `history.location` as your dependency in the useEffect, which will trigger the useEffect whenever the path changes. See: https://stackoverflow.com/questions/41911309/how-to-listen-to-route-changes-in-react-router-v4 – Danyal Apr 06 '20 at 16:46
  • Thank you so much, I used useLocation and now the design makes more sense. All user related actions are dealt within the user's context. Thank you. – sakib11 Apr 06 '20 at 17:31
  • 1
    Glad it solved your problem. I'll update the answer so anyone in future can easily solve it too. – Danyal Apr 06 '20 at 17:32
  • No worries. I already posted an answer but if you update yours, I'll hit accept. – sakib11 Apr 06 '20 at 17:34
0

Use useLocation from 'react-router-dom'. Then let location = useLocation() and useEffect(function(){//do stuff}, [location]). This way, we will run the useEffect when the url changes.

sakib11
  • 376
  • 3
  • 13