I'm actually building an SPA using React and Symfony and I'm facing an issue with the login part.
react-router-dom 5.2.0
history 4.10.1
I'm trying to redirect the user after successfully logged in to a private route /client
but React gives me errors when I try to redirect with history
or with the <Redirect />
component.
Also, when I redirect to a public route there isn't any error.
I found out that the loop comes from setUser
from the AuthContext.
// App.jsx
const App = () => {
const cart = getLocalStorage('cart', []);
const [currentUser, setCurrentUser] = useState(null);
const menu = '';
useEffect(() => {
setCurrentUser(authenticationService.currentUser)
}, [currentUser])
return <AuthProvider value={{ currentUser }}>
<MenuProvider value={{ menu }}>
<CartProvider value={{ cart }}>
<ToastProvider
autoDismiss
autoDismissTimeout={6000}
>
<Router history={history}>
< Navigation />
</Router>
</ToastProvider>
</CartProvider>
</MenuProvider>
</AuthProvider>
};
// Navigation.jsx
export const Navigation = () => {
const [currentUser, setCurrentUser] = useState(null);
const { user, setUser } = useContext(AuthContext)
useEffect(() => {
setCurrentUser(user)
}, [user])
const logout = () => {
authenticationService.logout();
setUser(null)
history.replace('/');
}
return <><div className="header">
<Container>
<Navbar expand="lg" sticky="top">
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto">
<NavLink className={"nav-link"} to={"/about"}> A propos </NavLink>
</Nav>
<Nav>
{currentUser && currentUser.username ?
<>
<Link className={"nav-link"} to={"/client"}> Menu </Link>
<NavDropdown title="Compte" id="collasible-nav-dropdown">
<NavDropdown.Item to={"/profile"} as={Link}>
Profile
</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item onClick={logout} to={"/logout"}> Déconnexion </NavDropdown.Item>
</NavDropdown>
</>
:
<NavLink className={"nav-link"} to={"/login"}> Connexion </NavLink>
}
</Nav>
</Navbar.Collapse>
</Navbar >
</Container>
</div>
<Switch>
<Route exact path="/" component={withRouter(Home)} />
<Route exact path="/home" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/logout" />
<Route exact path="/login" component={LoginPage} />
<Route exact path="/register" component={RegisterPage} />
<Redirect from="/logout" to="/home" />
<PrivateRoute exact path="/client" component={Client} />
</Switch>
</>
}
// LoginPage.jsx
const LoginPage = () => {
const [values] = useState(initialValues);
const [isLoading, setIsLoading] = useState(false);
const [redirect, setRedirect] = useState(false);
const { user, setUser } = useContext(AuthContext);
const { register, handleSubmit, formState: { errors }, reset } = useForm({ mode: 'all' });
const { addToast } = useToasts();
const { from } = { from: { pathname: "/client" } };
const onSubmit = async data => {
setRedirect(true);
setIsLoading(true);
await authenticationService.login(data);
setUser(await authenticationService.currentUser.username);
addToast(`Welcome back ${data.username} !`, {
appearance: 'success',
autoDismiss: true,
})
setIsLoading(false);
history.push(from);
reset();
}
return user ?
<Redirect to="/client" />
: (<Container>
<div className="row">
<div className="col-lg-8 mx-auto">
<Card>
<Card.Header>
<Row style={{ justifyContent: 'center' }}>
<Card.Title
style={{ marginTop: 'auto', marginBottom: 'auto' }}>
— Connexion
</Card.Title>
</Row>
</Card.Header>
<Card.Body>
<Form noValidate onSubmit={handleSubmit(onSubmit)}>
<Form.Group>
<Form.Row>
<Col md="5" style={{ textAlign: 'end', marginTop: 'auto', marginBottom: 'auto' }}>
<Form.Label
required
className="bold">
Code utilisateur
</Form.Label>
<FontAwesomeIcon icon={faEdit} />
</Col>
<Col md="6">
<Form.Control
{...register("username",
{
required: true
})}
type="text"
id="username"
defaultValue={values.username}
placeholder="Saisissez votre code utilisateur" />
</Col>
</Form.Row>
</Form.Group>
<Form.Group>
<Form.Row>
<Col md="5" style={{ textAlign: 'end', marginTop: 'auto', marginBottom: 'auto' }}>
<Form.Label
required
className="bold">
Mot de passe
</Form.Label>
<FontAwesomeIcon icon={faLock} />
</Col>
<Col md="6">
<Form.Control
{...register("password",
{
required: true,
minLength: 6,
// pattern: /[A-Za-z]{3}/,
})}
type="password"
id="password"
placeholder="Saisissez votre mot de passe" />
</Col>
</Form.Row>
</Form.Group>
<Card.Footer className="text-center">
{
isLoading ? (
<Button type="submit" className="btn-sample" disabled={isLoading}>
<span className="fa-1x">
<i className="fas fa-circle-notch fa-spin"></i>
</span>
<span style={{ padding: '0.5em' }}>Connexion</span>
<FontAwesomeIcon icon={faUser} />
</Button>
) : (
<Button type="submit" className="btn-sample">
<span style={{ padding: '1em' }}>Connexion</span>
<FontAwesomeIcon icon={faUser} />
</Button>
)
}
</Card.Footer>
</Form>
</Card.Body>
</Card>
</div>
</div>
</Container>)
}
The errors:
Uncaught (in promise) Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function
Any idea ?
Thank you so much ^^