GLHF
GLHF

Reputation: 4035

React state updating but rendering late

I've tried almost every solution similar to my problem, yet none is working. I have a simple state and changing the value of this state in a function as like below, handleOnClick is calling in a button's onClick event. I'm also using Router(if it's change something);

    import { useState} from "react"
    import { BrowserRouter as Router, Route, Link, useHistory} from "react-router-dom";
    
    const Buton = () => {
    
    let x = "";

    const [lowerState, setLower] = useState("")
    
    const history = useHistory();
    
    const handleOnClick = () => {
        x = document.getElementById("my_input").value.toLowerCase();
            
        setLower(x)

        console.log(x) //this prints the current value
        console.log(lowerState) //this DOES NOT prints the current value, but 
                                // when I put another text into the input and click
                                // to button, it prints the first value I put here
            
        history.push('/test', {params : lowerState})
    };
      
     .
     .
     .
    return (...)
}
export default Buton

Now x is a value that returns from an input HTML element. When I set this value as a state and console log, it doesn't print the value first, when I put something in input again, then it prints the first value. So it's like it's coming 1 step behind.

I've used useEffect() , I did put a second parameter to setLower as console.log(lowerState) and other things on the internet that people suggested, but none is working. Every time, the state is coming 1 step behind. How can I make this state changes immediately?

Upvotes: 1

Views: 1574

Answers (5)

ChronoLink
ChronoLink

Reputation: 388

If you want to use the value of an input in a user event function, the best way (and least buggy) is to bind your input value to local state and then just reference that state in your callback function.

Please try to avoid imperatively pulling values from the DOM using getElementById etc. Here's what I mean:

const [value, setValue] = useState('');

// This will keep everything updated until you need to use it
handleChange(event) {
  setValue(event.target.value);
}

// Then just grab whatever is in local state
handleClick() {
  history.push('/test', {params : value});
}

return (
  <input value={value} onChange={handleChange} />
  // Your button is here too
)

Upvotes: 2

Anshuk
Anshuk

Reputation: 9

So i would like to suggest that use useRef if need for reference only object which may not causing rerendering. also using let x= "" is not correct, you should write code immutable way

const Buton = () => {
    
   const lowerCaseRef = useRef("")
    
    const history = useHistory();
    
    const handleOnClick = () => {
        lowerCaseRef.current = 
        document.querySelector("#my_input").value.toLowerCase();
        console.log(lowerCaseRef.current) //this DOES NOT prints the current value, but 
                                // when I put another text into the input and click
                                // to button, it prints the first value I put here
            
        history.push('/test', {params : lowerCaseRef.current})
    };
      
   
    return (...)
}

Upvotes: 0

Ryan Le
Ryan Le

Reputation: 8412

This is expected behaviour since React is updating state in a batch

Which mean that the state only gets an update after an eventHandler/function is finished

If you want to do some condition, wrap your logic inside a useEffect

useEffect(() => {
  if (lowerState === "your-condition-value") {
    history.push("/test", { params: lowerState });
  }
}, [lowerState]);

Or in your case, just use the variable directly:

const handleOnClick = () => {
  x = document.getElementById("my_input").value.toLowerCase();
  history.push("/test", { params: x });
};

You should not worry about that since your app still working as expected

Upvotes: 0

tuomokar
tuomokar

Reputation: 388

When you call setLower(x), it doesn't immediately update the lowerState. The new value will be available the next time it renders. Because of that the console.log(x) "works" (since it uses the new value that you gain as a parameter) but console.log(lowerState) uses the old value that hasn't updated to the state yet at that point.

If you want history.push('/test', {params : lowerState}) to work, then you need to use the x there instead of lowerState. Or call it within a useEffect with the lowerState and having lowerState as a dependency parameter to the hook.

Upvotes: 1

moshfiqrony
moshfiqrony

Reputation: 4723

This is because when you call setLower(x) it is not an async call. So it doesn't wait. That's why you get the 1 step before value in your state right after setting the value.

Official doc - https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

Upvotes: 1

Related Questions