0

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' }}>
                                    &mdash; 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 ^^

tanina
  • 1
  • 1
  • ```useEffect``` runs after a render. Setting state causes a re-render - so what you are doing is an infinite loop (but react is stopping you with the error) -- because you are setting state in ```useEffect``` by calling: ```setCurrentUser(user)``` ```https://stackoverflow.com/questions/53715465/can-i-set-state-inside-a-useeffect-hook``` – developer Apr 30 '21 at 09:39
  • Well, I did remove the `useEffect` which, in fact, is not necessary because I get the user from the context. But, still getting the same errors ! – tanina Apr 30 '21 at 09:44
  • both of them? - you have same configuration in both your App and your Navigation components... – developer Apr 30 '21 at 09:47
  • yes, I just replaced the `useEffect` in App component with `authenticationService.currentUser` – tanina Apr 30 '21 at 09:52
  • still same errors using localStorage to set the user in App component ! – tanina Apr 30 '21 at 09:54

1 Answers1

0

You don't have to pass currentUser to the dependencies array. You can pass either empty array or [authenticationService.currentUser]

Jatin Parate
  • 528
  • 5
  • 9
  • Thank you for your answer. The error is coming from `setUser` in LoginPage which updates the AuthContext, but I do not see why it leads to an infinite loop. – tanina Apr 30 '21 at 10:43