1

I have that simple code:

const Login = (): JSX.Element => {
  const {errors}= useAppSelector(state => state.errorsReducer)
  const dispatch = useAppDispatch();

  const navigate = useNavigate();
  
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const onSubmit = (e: FormEvent) => {
    e.preventDefault();
  };

  const login = () => {
    dispatch(errorsSlice.actions.clearErrors());
    console.log(errors);
    const userData = {
      email: email,
      password: password
    }
    
    if(!userData.email || !userData.password){
      dispatch(errorsSlice.actions.addError('Email or password is undefined'));
    }

    if(errors.length === 0){
      axios
        .post('http://localhost:7000/auth/login', userData)
        .then(response =>{
          const decodedToken: IUser = jwt_decode(response.data.token);
          localStorage.setItem('userData', JSON.stringify(decodedToken));
          localStorage.setItem('token', JSON.stringify(response.data.token));
          navigate('/')
        })
        .catch(e => {
          const errorMessage = e?.response?.data?.message
          dispatch(errorsSlice.actions.addError(errorMessage));
        });
    }
    console.log(errors);
  }

    return (
      <main className="form-signin m-auto"  style={{height: 600}}>
        <div className='container h-100 d-flex align-items-center justify-content-center'>
          <form onSubmit={(e) => onSubmit(e)} style={{width: 400}}>
            <h1 className="h3 mb-3 fw-normal">Auth</h1>

            <div className="form-floating">
              <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className="form-control" id="floatingInput" placeholder="name@example.com"/>
              <label htmlFor="floatingInput">Email</label>
            </div>
            <div className="mt-2 form-floating">
              <input type="password" className="form-control" value={password} onChange={(e) => setPassword(e.target.value)} id="floatingPassword" placeholder="Password"/>
              <label htmlFor="floatingPassword">Password</label>
            </div>

            <div className="checkbox mt-2 mb-3">
              <label>
                Or <Link to='/registration'>click that</Link> to registration. 
              </label>
            </div>
            <button className="w-100 btn btn-lg btn-primary" onClick={() => login()} type="submit">Login</button>
          </form>
        </div>

        <>
          {errors.map((error) => {
              return <MyToast
                text = {error}
                color = {'bg-danger'}
                show = {true}
                key = {error}
              />
            })}
          </>
        
    </main>
    )
}

On click Login button call function login() which in case no errors send request on server. I testing requests with errors. That works on first time, dispatch set error to store and all works good, but on second click should work dispatch(errorsSlice.actions.clearErrors()), but his work with delay and when my function go to expression if(errors.length === 0), then expression return false, because errors.length = 1, although dispatch(errorsSlice.actions.clearErrors()) should have worked. What me need do, to that code works true?

rjunovskii
  • 23
  • 5
  • Does this answer your question? [When i dispatch action to redux from react in line 1 can i be 100% sure that line 2 in my react component has the updated redux state?](https://stackoverflow.com/questions/46069701/when-i-dispatch-action-to-redux-from-react-in-line-1-can-i-be-100-sure-that-lin) – gerrod Mar 02 '23 at 21:46

1 Answers1

1

The selected errors state value is closed over in login callback scope, it will never be a different value in the callback. You can't dispatch actions to update the state and expect the closed over values to change.

I suspect you are really just wanting to validate the inputs and dispatch the addError action if there are issues, otherwise continue with the login flow. I suggest a small refactor to compute and check local errors to avoid the issue of the stale closure over the selected errors state.

Example:

const Login = (): JSX.Element => {
  const { errors } = useAppSelector(state => state.errorsReducer)
  const dispatch = useAppDispatch();

  const navigate = useNavigate();
  
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const onSubmit = async (e: FormEvent) => {
    e.preventDefault();
  
    dispatch(errorsSlice.actions.clearErrors());
    
    // check for field data issue, early return
    if (!email || !password){
      dispatch(errorsSlice.actions.addError('Email or password is undefined'));
      return;
    }

    // no field data issues, try authenticating
    try {
      const userData = { email, password };
      const response = await axios.post(
        "http://localhost:7000/auth/login",
        userData
      );

      const decodedToken: IUser = jwt_decode(response.data.token);
      localStorage.setItem('userData', JSON.stringify(decodedToken));
      localStorage.setItem('token', JSON.stringify(response.data.token));
      navigate('/');
    } catch(e) {
      const errorMessage = e?.response?.data?.message
      dispatch(errorsSlice.actions.addError(errorMessage));
    };
  }

  return (
    <main className="form-signin m-auto"  style={{ height: 600 }}>
      <div className="container h-100 d-flex align-items-center justify-content-center">
        <form onSubmit={onSubmit} style={{width: 400}}>
          <h1 className="h3 mb-3 fw-normal">Auth</h1>

          <div className="form-floating">
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              className="form-control"
              id="floatingInput"
              placeholder="name@example.com"
            />
            <label htmlFor="floatingInput">Email</label>
          </div>
          <div className="mt-2 form-floating">
            <input
              type="password"
              className="form-control"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              id="floatingPassword"
              placeholder="Password"
            />
            <label htmlFor="floatingPassword">Password</label>
          </div>

          <div className="checkbox mt-2 mb-3">
            <label>
              Or <Link to='/registration'>click that</Link> to registration. 
            </label>
          </div>
          <button className="w-100 btn btn-lg btn-primary" type="submit">
            Login
          </button>
        </form>
      </div>
      <>
        {errors.map((error) => (
          <MyToast
            key={error}
            color="bg-danger"
            show
            text={error}
          />
        ))}
      </>
    </main>
  )
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Wow, that literally working!) Thank you sou much :) But i dont understand, why my decision working wrong? – rjunovskii Mar 05 '23 at 10:17
  • 1
    @rjunovskii Your original implementation wasn't working because the callback was referencing a variable from the outer scope of the component that won't ever be updated to a newer value during the life of the callback. In other words, dispatching actions to update the selected `errors` state won't be available in the same callback scope. – Drew Reese Mar 07 '23 at 18:11