2

So, I have a controlled input component and have a div that takes another state variable. The two states are updated in a single call-back function. Why the value in the input and the text in the div below are not synched?

`import React, {useState, useRef} from "react";

const Input =()=>{
    const [search, setSearch] = useState('jk');
    const [text, setText] = useState('');

 const onChange =(e)=>{
   setSearch(e.target.value) 
   setText(search)  
  }

return(
    <>
      <input type='text' value={search} onChange={onChange} />
      <div>{text}</div>
        </>
)
};

export default Input`

I know about closure and stale state, but wasn't react 18's automatic batching was supposed to solve this?

Eli K
  • 21
  • 2
  • 3
    automatic batching is, updating the state after all the setState's are ran and then setting them at last one by one .... so the `search` updated value is not available yet and available in next render and automatic batching has nothing to do with it .. – KcH Oct 26 '22 at 07:08

1 Answers1

1

React state updates are immutable. When you setSearch(e.target.value), you are not mutating the search variable within the component's closure. The search variable will not reflect your new value until after a subsequent render. Otherwise, we'd all just use regular variables and mutate them as needed, but then React would have no way of knowing that it should rerender.

Just to be more explicit about this:

const [search, setSearch] = useState('jk');
const [text, setText] = useState('');

const onChange = (e) => {
  // the `search` variable above will not reflect this new value until the next render
  setSearch(e.target.value)

  // false (unless they typed "jk" again)
  search === e.target.value

  // you are still using the old search value from the last render when search was defined as "jk"
  setText(search)  
}

There are two ways around this. One is to simply set them both to the same value:

const newSearch = e.target.value
setSearch(newSearch)
setText(newSearch)

^ I personally prefer that one in this example because it is simple and should result in only a single rerender, but another pattern is to use useEffect, since the text being displayed is derived from the search value, or in other words you want to update the text as a side-effect of the search value changing.

const onChange = (e) => {
  setSearch(e.target.value)
}

// whenever search changes, update the text
useEffect(() => {
  setText(search)
}, [search])
Lokua
  • 576
  • 5
  • 15