Home > Net >  Authentication with react context and react router v6 problem
Authentication with react context and react router v6 problem

Time:11-18

I'm trying to build an authentication service using react context with react router v6 routes and I can´t go to the expected route. So, my login in service is working, I'm retrieving the user from the back and setting in localStorage, also have a state from context to store the user data (loggedUser), with only id and type at the moment. Probably is something silly but the loggedUser is not updated in the routes file and I don't understand why, and if it's not the context than it's the routing, with the "Warning: Cannot update a component (BrowserRouter) while rendering a different component (ClientRoute).". Thanks in advance, for any help!

Here is the code that I'm using: AuthContext.jsx file (don't know if used the useEffect correctly with storing the user too)

import React, { createContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import user_api from "../Middles/user_api";

export const AuthContext = createContext({});

//Context criado para autenticação, definir permissão de usuário para determinadas rotas
export const AuthProvider = ({ children }) => {

  const navigate = useNavigate();
  const [loggedUser, setLoggedUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const cachedUser = localStorage.getItem("user");
    if(!cachedUser){
      console.log("no user");
    } else {
      console.log("user logged ", cachedUser);
    }
    if(loggedUser){
      localStorage.setItem("user", JSON.stringify(loggedUser));
      setIsLoading(false);
      navigate('../home', { replace: true });
    }
  },[loggedUser]);

  const login = async (loginValues) => {
    try {
      const response = await user_api.loginUser(loginValues);
      console.log(response.data);
      const { userData, token } = response.data;
      const user = {
        id: userData.id,
        type: userData.data.type
      }
      setLoggedUser(user);
    } catch (error) {
      console.log(error);
    }
  }
  
  const logout = () => {
    console.log("logout");
  }

  return(
    <AuthContext.Provider value={ {isAuthenticated: !!loggedUser, loggedUser, isLoading, login, logout} }>
      {children}
    </AuthContext.Provider>
  )
}

Routes file (App_router in index.jsx)

import React, { useContext, useState } from 'react';
import { AuthContext, AuthProvider } from '../Contexts/AuthContext';
import {
  BrowserRouter as Router,
  Route,
  Routes,
  useNavigate
} from "react-router-dom";

import HomePage from '../Pages/HomePage';
import Exercises from '../Pages/Exercises';
import UserForm from '../Pages/Forms/UserForm';
import Login from '../Pages/Login';


const App_Router = () => {  
  
  const navigate = useNavigate();
  const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);
  
  const ProRoute = ({ children }) => {

    const cachedUser = JSON.parse(localStorage.getItem("user"));
    if(cachedUser && cachedUser.type == 'Profissional'){
      return children;
    }
    
    if(isLoading){
      return <section>Carregando...</section>;
    }
    
    if(isAuthenticated || loggedUser?.type == "Profissional"){
      return children;
    } 
    else {
    console.log("Usuário não autorizado.");
    navigate('/login', {replace: true});
    }
  }
  
  
  const ClientRoute = ({ children }) => {
    
    const cachedUser = JSON.parse(localStorage.getItem("user"));
    console.log("cachedUser ", cachedUser);
    if(cachedUser && cachedUser.type == 'Profissional'){
      return children;
    }

    if(isLoading){
      return <section>Carregando...</section>;
    }

    if(isAuthenticated || loggedUser?.type == "Cliente"){
      return children;
    } 
    else {
    console.log("Usuário não autorizado.");
    navigate('/login', {replace: true});
    }
  }

  return (
    <AuthProvider>
      <Routes>
        <Route index path='/' element={<Login />} />
        <Route exact path='/login' element={<Login/>}/>
        <Route exact path='/register' element={<UserForm/>}/>
        <Route
          path='/home/Profissional'
          element={
            <ProRoute>
            <HomePage/>
            </ProRoute>
          }/>
        <Route
          path='/home'
          element={
            <ClientRoute>
              <HomePage/>
            </ClientRoute>
        }/>
          <Route
            path='/home/Profissional/exercises'
            element={
              <ProRoute>
                <Exercises/>
              </ProRoute>
            }/>
      </Routes>
    </AuthProvider>
  )
}

export default App_Router;

Login file

import React, { useContext, useState } from 'react'
import { Link } from 'react-router-dom';

import { AuthContext } from '../../Contexts/AuthContext';
import user_api from '../../Middles/user_api';

import './styles.css'

//Componente de Login usado para autenticação e condicionamento de rotas, autenticação de usuário
const Login = () => {

  const { login } = useContext(AuthContext);
  const [values, setValues] = useState({
    email: '',
    password: '',
    usertype: ''
  });

  const handleValues = (e) => {
    setValues({...values, [e.target.name]: e.target.value});
  }

  const handleSubmit = async (e) => {
    e.preventDefault();
    login(values)
      .then(
        res => {
          setValues({
          email: '',
          password: '',
          usertype: ''
          });
        })
  }

  return (
    <main id="login-wrapper">
      <aside>Chamada de API de treino</aside>
      
      <section id="login-container">
        <form onSubmit={handleSubmit}>
          <label htmlFor="email">Email</label>
          <input type="email" name='email' value={values.email} onChange={handleValues}/>
          <label htmlFor="password">Senha</label>
          <input type="password" name='password' value={values.password} onChange={handleValues}/>
          <a href="">Esqueceu a senha?</a>
          <fieldset>
            <legend>Tipo:</legend>
            <input type="radio" name='usertype' checked={values.usertype === 'Profissional'} value='Profissional' onChange={handleValues} />
            <label htmlFor="usertype">Profisisonal</label>
            <input type="radio" name='usertype' checked={values.usertype === 'Cliente'} value='Cliente' onChange={handleValues} />
            <label htmlFor="">Aluno</label>
          </fieldset>
          <button type='submit'>Entrar</button>
        </form>
      </section>

      <aside>
      <h4>Ainda não possui conta?</h4>
      <Link to="/register">Cadastre-se</Link>
      </aside>

  </main>
  )
}

export default Login;

Here is the full error log that pops when I try to login:

react_devtools_backend.js:4026 Warning: Cannot update a component (`BrowserRouter`) while rendering a different component (`ClientRoute`). To locate the bad setState() call inside `ClientRoute`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
    at ClientRoute (http://localhost:8888/src/Routes/index.jsx:55:5)
    at RenderedRoute (http://localhost:8888/node_modules/.vite/deps/react-router-dom.js?v=f7b60e3a:2434:5)
    at Routes (http://localhost:8888/node_modules/.vite/deps/react-router-dom.js?v=f7b60e3a:2744:5)
    at AuthProvider (http://localhost:8888/src/Contexts/AuthContext.jsx:22:3)
    at App_Router (http://localhost:8888/src/Routes/index.jsx:26:20)

Please help, I'm relative new to react and this would be much appreciated. Thanks!

CodePudding user response:

The main issue with the code is that the protected route components are calling navigate as an unintentional side-effect, i.e. outside the useEffect hook. They should either call navigate from the useEffect hook or as a more conventional method just render the Navigate component. A secondary issue that may or may not be contributing to any issues is that both the ProRoute and ClientRoute components are being declared inside another React component; this is generally considered an anti-pattern.

Declare the route protection components on their own and rewrite them to render the Navigate component and consume the AuthContext.

App_Router

import {
  BrowserRouter as Router,
  Route,
  Routes,
  Navigate,
  Outlet,
} from "react-router-dom";

const ProRoute = ({ children }) => {
  const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);

  if (isLoading) {
    return <section>Carregando...</section>;
  }

  if (isAuthenticated || loggedUser?.type == "Profissional") {
    return children || <Outlet />;
  } else {
    return <Navigate to='/login' replace />;
  }
}

const ClientRoute = ({ children }) => {
  const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);

  if (isLoading) {
    return <section>Carregando...</section>;
  }

  if (isAuthenticated || loggedUser?.type == "Cliente") {
    return children || <Outlet />;
  } else {
    return <Navigate to='/login' replace />;
  }
}

const App_Router = () => {  
  return (
    <AuthProvider>
      <Routes>
        <Route path='/' element={<Login />} />
        <Route path='/login' element={<Login />} />
        <Route path='/register' element={<UserForm />} />
        <Route path='/home'>
          <Route element={<ClientRoute />}>
            <Route index element={<HomePage />} />
          </Route>
          <Route element={<ProRoute />}>
            <Route path='Profissional'>
              <Route index element={<HomePage />} />
              <Route path='exercises' element={<Exercises />} />
            </Route>
          </Route>
        </Route>
      </Routes>
    </AuthProvider>
  );
}

export default App_Router;
  • Related