0

I've never really noticed this before; not sure if theres a bug in my code or if onChange always behaves this way, but I'm getting an input delay and am not sure how to resolve it. Heres an example of what I mean:

enter image description here

As you can see the console only prints the previous state which is a nuisance because that means to have the user log in they have to click the "Go" button twice for it to receive the correct state, even if the username and password has been correctly input.

My code is fairly straight forward:

const [state, setState] = useState<LoginStateType>(iLoginState)
const {username, password} = state

const handleChange = (event: ChangeEventType) => {
    const {name, value} = event.target
    setState({...state, [name]: value})
    console.log(username)
}

{...}

<input type="text" name="username" value={username} onChange={handleChange}/>
<input type="text" name="password" value={password} onChange={handleChange}/>

Any help is appreciated, thanks!

jmsapps
  • 388
  • 2
  • 17

3 Answers3

1

Since State Updates May Be Asynchronous.


With hooks, this can be achieved by

const [state, setState] = useState(/* ... */)

setState(/* ... */)

useEffect(() => {
  // state is guaranteed to be what you want
}, [state])
equt
  • 192
  • 1
  • 8
  • I'm sorry I don't understand, can you show me an example? – jmsapps May 29 '22 at 07:19
  • Oh, i somehow confuse the hooks with the old class component. But this is still doable, and has been already answered, see https://stackoverflow.com/a/67408346/15342940 – equt May 29 '22 at 07:39
  • Thank you, that was very helpful. I ended up using useRef instead, I posted it below. Feel free to edit or comment on it if you feel there is a better way that I can achieve what im trying to do. – jmsapps May 29 '22 at 16:22
0

setState is a async step, not get new value immediately!

wenzi
  • 348
  • 2
  • 10
0

What I ended up doing was perhaps a bit cumbersome but it gets the job done. Aside from the state (which is necessary for the useContext I have set up) I also set up two useRef constants:

const inputUserName = useRef<HTMLInputElement | null>(null)
const inputPassword = useRef<HTMLInputElement | null>(null)

{...}

<input type="text" name="username" ref={inputUserName} value={username} onChange={handleChange}/>
<input type={inputType as string} ref={inputPassword} name="password" value={password} onChange={handleChange}/>

So the full code I have is as follows:

  //-------------ADMIN STATE-------------
  const [state, setState] = useState<LoginStateType>(iLoginState)
  const {username, password} = state

  //-------------USE EFFECT-------------
    //UseEffect: this is called when the context is set, I think
  useEffect(() => {
    //dispatch must be called before submit, I think because its an asynchronous call
    dispatch(getAdminsRedux());
        setContext(localStorage.getItem('user'))
        if (context !== null){
          navigate("/admin", {replace: true})
        }

  }, [setContext, context, dispatch, navigate]);

  //-------------HANDLE CHANGE-------------
  const handleChange = (event: ChangeEventType) => {
    const {name, value} = event.target
    setState({...state, [name]: value})
  }

  //-------------HANDLE LOGIN-------------
  const handleLogin = async (event: MouseEvent) => {
    event.preventDefault()

    admins.forEach(async (admin)  => {
      if(admin.username === inputUserName.current!.value){
        const comparedHash = compareHash(inputPassword.current!.value, admin.password)
        if (comparedHash){
          setContext(state.username)
          localStorage.setItem('user', state.username)
        }
      }
    });
  }
jmsapps
  • 388
  • 2
  • 17