0

I'm new to React and I'm trying to create a user state global context using a context provider so that on submission of the login form, the username and role are updated in the global context with the response of a successful request.

The code for my app is as follows:

import {BrowserRouter,Routes,Route} from 'react-router-dom';

//PAGES AND COMPONENTS
import Registration from './components/registration/Registration';
import Home from './components/home/Home';
import Login from './components/login/Login';

//GLOBAL CONTEXTS
import {AuthProvider} from './context/AuthContext'


function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <AuthProvider>  
          <Routes>
              <Route path='/' element={<Home/>}></Route>
              <Route path='/Register' element={<Registration/>}></Route>
              <Route path='/Login' element={<Login/>}></Route>
          </Routes>
        </AuthProvider>
      </BrowserRouter>
    </div>
  );
}

export default App;

The code for my Provider (AuthContext.js) is the following:

const AuthContext= React.createContext();
const AuthUpdateContext = React.createContext();

export function useAuth(){
    return useContext (AuthContext);
}

export function useAuthUpdate(){
    return useContext(AuthUpdateContext);
}

export function AuthProvider({children}){
    const [authState,setAuthState]=useState({
                                                id:0,
                                                username:"",
                                                role:"user"
                                            });

   
    

    return(
        <AuthContext.Provider value={authState}>
            <AuthUpdateContext.Provider value={setAuthState}>
                {children}
            </AuthUpdateContext.Provider>    
        </AuthContext.Provider>
    )
};

And the code that's throwing the Invalid Hook error is the following:

import {useNavigate} from 'react-router-dom'
import {Formik, Form, Field,ErrorMessage} from 'formik'
import axios from 'axios';
import * as Yup from 'yup';
import './Login.css';
import {useAuth,useAuthUpdate} from '../../context/AuthContext'


function Login() {
  const navigate=useNavigate();
  const [invalidCredentials,setInvalidCredentials]=useState("");
  const setAuthState=useAuthUpdate;
  const useAuthState=useAuth();

  
  const onSubmit=(data)=>{
      axios
            .post('http://localhost:3001/User/Login',data)
            .catch((error)=>{
              setInvalidCredentials("Invalid Credentials. Please try again.")
            })
            .then((response)=>{
              localStorage.setItem("token",response.data.token);
              setAuthState((prev)=>({
                ...prev,
                id:response.data.id,
                username:response.data.username,
                role:response.data.role
              }))
              

            })
            .then(()=>{
              console.log(useAuthState);
              navigate('/');
            })
  }

  const validationSchema=Yup.object().shape({
    usernameOrEmail:Yup
                .string()
                .required('Field must not be blank.'),
    password:Yup.string()
                .required('Field must not be blank.'),
});

  const initialValues ={
    usernameOrEmail:"",
    password:"",
        };

  return (
    <div className='container'>
      <div className='row'>
        <h1>Welcome Back!</h1>
      </div>

      <div className='row'>
        <h2>Log-in Here:</h2>
      </div>

      <Formik onSubmit={onSubmit} validationSchema={validationSchema} initialValues={initialValues}>
        <Form>
          <div className='row'>
            <label>Username/Email</label>
            <Field name='usernameOrEmail'></Field>
            <ErrorMessage name="usernameOrEmail">
                                    {msg=><div className="errorMsg">{msg}</div>}
            </ErrorMessage>
          </div>
          <div className='row'>
            <label>Password</label>
            <Field  name='password'
                    type='password'/>
            <ErrorMessage name="password">
                                    {msg=><div className="errorMsg">{msg}</div>}
            </ErrorMessage>
          </div>
          
          <div id="register-btn"className="row">
                    <button className="btn btn-secondary" type="submit" >Login</button>
          </div>

          <div className='row'>
            <span className='errorMsg'>{invalidCredentials}</span>
          </div>
        </Form>
      </Formik>
    </div>
  )
}

export default Login

What I find weird is that both useAuthState and setAuthState are React Hooks, and I've positioned them in the same way, but when I console.log useAuthState, it logs it as : {id:0, username:"", role:"user"}, whereas when I call setAuthState, it throws me the following error:

Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem. at Object.throwInvalidHookError (react-dom.development.js:16227:1) at useContext (react.development.js:1618:1) at useAuthUpdate(AuthContext.js:12:1) at Login.js:25:1
  • 1
    I think you meant to use `const setAuthState = useAuthUpdate()` (you are missing the `()`). FYI, you don't need to use two contexts; it's reasonably common to put both state and setter in the same context – Phil Sep 19 '22 at 23:31
  • When I do that, I don't get the error, but the console.log(useAuthState) returns to me an unchanged object. That is: "{id: 0, username: '', role: 'user'}". The reasoning behind not putting parentheses is that I want to import the function setAuthState, not the result of it. Am I wrong in thinking that? – SpicyRiceTea Sep 19 '22 at 23:39
  • 1
    [State updates are asynchronous](https://stackoverflow.com/a/54069332/283366). Stop using `console.log()` to verify state and start using your actual application – Phil Sep 19 '22 at 23:43
  • 1
    You were absolutely right on both counts! I used react dev tools and they showed that the component WAS updating. I'm still left wondering why I needed to add the parentheses after useAuthUpdate. Shouldn't useAuthUpdate==setAuthState since useContext returns the value of the context? – SpicyRiceTea Sep 20 '22 at 00:15
  • Because `useAuthUpdate` is a function that returns the context value which is itself, a function – Phil Sep 20 '22 at 00:16

0 Answers0