Reputation: 295
Edited again to explain that ===
compares the memory location of objects (Why are two identical objects not equal to each other?) which is why the useEffect()
kept running again and again and again... I think I have it sorted now but will leave the post here without an answer for a while in case I'm talking absolute gibberish and someone would like to correct me :)
--- End of second edit ----
I have edited my Routes
const to log the different sessions
to the console. As you can see, they are equal but for some reason, the computer doesn't seem to think so. Does anyone have any idea why???
edited Routes (to show the values)
const Routes = () => {
const [session, setSession] = useState(getSessionCookie());
console.log('Route session before: ', session);
useEffect(
() => {
//setSession(getSessionCookie());
const stateCookie = session;
const getCookie = getSessionCookie();
console.log('stateCookie: ', stateCookie);
console.log('getCookie: ', getCookie);
console.log('Are equal? ', stateCookie === getCookie);
},
[session]
);
console.log('Route session after: ', session);
----- End of edit -------
I found a tutorial online to assist me in managing user sessions but I keep having a problem with the re-rendering of the component unless I take out the useEffect dependency. I've logged the values and types of the session
variable but it doesn't change so I don't understand why the re-render keeps happening. Any help would be greatly appreciated.
index.js
import React, { useEffect, useState, useContext } from 'react';
import { render } from "react-dom";
import { Router, Switch, Route } from "react-router";
import { Link } from "react-router-dom";
import { createBrowserHistory } from "history";
import Cookies from "js-cookie";
import { SessionContext, getSessionCookie, setSessionCookie } from "./session";
const history = createBrowserHistory();
const LoginHandler = ({ history }) => {
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async e => {
e.preventDefault();
setLoading(true);
// NOTE request to api login here instead of this fake promise
await new Promise(r => setTimeout(r(), 1000));
setSessionCookie({ email });
history.push("/");
setLoading(false);
};
if (loading) {
return <h4>Logging in...</h4>;
}
return (
<div style={{ marginTop: "1rem" }}>
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Enter email address"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<input type="submit" value="Login" />
</form>
</div>
);
};
const ProtectedHandler = ({ history }) => {
const session = useContext(SessionContext);
if (session.email === undefined) {
history.push("/login");
}
return (
<div>
<h6>Protected data for {session.email}</h6>
<Link to="/logout">Logout here</Link>
</div>
);
};
const LogoutHandler = ({ history }) => {
useEffect(
() => {
Cookies.remove("session");
history.push("/login");
},
[history]
);
return <div>Logging out!</div>;
};
const Routes = () => {
const [session, setSession] = useState(getSessionCookie());
console.log('Routes session before: ', session);
console.log('Routes session before typeof: ', typeof session);
useEffect(
() => {
setSession(getSessionCookie());
},
[session] // <-------------- this is the dependency that seems to be causing the trouble
);
console.log('Routes session: ', session);
console.log('Routes session typeof: ', typeof session);
return (
<SessionContext.Provider value={session}>
<Router history={history}>
<div className="navbar">
<h6 style={{ display: "inline" }}>Nav Bar</h6>
<h6 style={{ display: "inline", marginLeft: "5rem" }}>
{session.email || "No user is logged in"}
</h6>
</div>
<Switch>
<Route path="/login" component={LoginHandler} />
<Route path="/logout" component={LogoutHandler} />
<Route path="*" component={ProtectedHandler} />
</Switch>
</Router>
</SessionContext.Provider>
);
};
const App = () => (
<div className="App">
<Routes />
</div>
);
const rootElement = document.getElementById("root");
render(<App />, rootElement);
session.js
import React from "react";
import * as Cookies from "js-cookie";
export const setSessionCookie = (session) => {
Cookies.remove("session");
Cookies.set("session", session, { expires: 14 });
};
export const getSessionCookie = () => {
const sessionCookie = Cookies.get("session");
if (sessionCookie === undefined) {
console.log('undefined');
return {};
} else {
return return JSON.parse(sessionCookie);
}
};
export const SessionContext = React.createContext(getSessionCookie());
Upvotes: 1
Views: 1906
Reputation: 1179
this is a very common issue when working with useEffect
, you should not have an object inside the dependency array, because Object just refers its reference, not actual value. And when session is created, it will have a new reference even when your prefer property value is the same => This is why it creates infinite loop.
If you put session
as a dependency, you should have explicitly compare the property value, like session.value
.
I'm not using context API much, but I guess there might be something wrong in your <Routes />
component, there might not need to update the session inside <Routes />
since it just plays as a provider role. Usually, this is the place you assign an initial context value.
Once user's logged in successfully, you can update session inside <LoginHandler />
. The other child components which consume the context value just need to use useContext
to get the latest session value.
So, basically your app might look like this:
// sessionContext.js
const SessionContext = React.createContext({
session: {},
setSession: () => {},
});
// components/routes.js
const Routes = () => {
const [session, setSession] = useState({})
const contextSession = {
session,
setSession
}
return (
<SessionContext.Provider value={contextSession}>
{children}
</SessionContext.Provider>
)
}
// components/childComponent.js
const ChildComponent = () => {
const { session } = useContext(SessionContext)
if (!session)
return null;
return <div>Logged-in</div>
}
For complicated state management, I suggest having a look at redux, you won't need to use context like the above example.
Upvotes: 1
Reputation: 42516
Given that you should only be logging the sessions when the session is updated/changed, you should do a comparison before updating the state, otherwise, it will cause an infinite loop as you are constantly updating the state.
useEffect(() => {
// assuming that session is not an array or object
if (getSessionCookie().email === session.email) {
return;
}
setSession(getSessionCookie());
}, [session]);
Likewise, on your LogoutHandler
component, you shouldn't update the history
object while having it as part of the dependency array. In fact, there is no need to call history.push()
, as you should already be in that route when the component is rendered. You should only remove the cookies once, thus you can call that when the component is mounted.
const LogoutHandler = ({ history }) => {
useEffect(() => {
Cookies.remove("session");
}, []);
return <div>Logging out!</div>;
};
Upvotes: 0