Automatic batching in React 18 is not working? - reactjs

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?

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])

Related

useState Hook inside loop or conditional statement

I was reading the updated react docs whose link is here there is statement related to useState
If you want to useState in a condition or a loop, extract a new
component and put it there.
But what I know by the rules of hooks is that you cannot use hooks inside loops, conditions, or nested functions which is mentioned in the docs here
So can someone give an example of the first sentence, because they didn't gave an example or there.
My question is can we call hooks inside loops and conditional statements ?
What they are saying is that if you want to have something with state in loop component:
const App = ()=>{
const fields = [someArray]
return(
<div>{fields.map((field)=>{
const [value, setValue] = useState() //wrong usage!
return (<input
onChange={(e)=>setValue(e)}
value={value}
/>)})})
</div>
)}
You need to specify component that you wanna loop in new Component and use hook there:
const Input = ()=>{
const [value, setValue]= useState()
return <input
onChange={(e)=>setValue(e)}
value={value}
/>
}
and use it in loop
const App = ()=>{
const fiels = [someArray];
return (
fields.map((field)=>
<Input />
)
)
}
const Input = ()=>{
const [value, setValue]= useState()
return <input
onChange={(e)=>setValue(e)}
value={value} */ Don't mutate state /*
/>
}
We should not be mutating state. React needs to compare it's virtual DOM to the current DOM to determine what to re-render. Futhermore, react hooks are asynchronous, meaning that value may not be updated to e at the point you try and assign to value. This was already achieved with setValue(e).

React state not in sync with the actual state

I have a very simple setup. I just have one controlled input and I want to console.log the input.
import React, {useState} from 'react'
const App = () => {
const [text, setText] = useState('')
const handleChange = event => {
event.preventDefault()
setText(_prev => event.target.value)
consoel.log(text)
}
return(
<div>
<input type="text" value={text} onChange={handleChange} />
</div>
)
}
I seem to only be getting the one before the actual state. For example if I type 'abc' in the console i only see 'ab' and after typing a fourth character I see 'abc'. Why is my state always one input behind?
(trying to change the state in the following manner setText(event.target.value) has provided the same results).
Due to it's asynchronous behavior you can't directly get value right after it's being updated. To get the value, you can use the effect hook which will check for the change and acts upon the behavior:
useEffect(() => {
console.log(text) // act upon
}, [text]) // when text changes, it runs
Also, side note, you just have to do:
setText(event.target.value)

Use of ref to prevent often-changing value from useCallback

In the Reach Hooks FAQ section "How to read an often-changing value from useCallback?" there is an example:
function Form() {
const [text, updateText] = useState('');
const textRef = useRef();
useEffect(() => {
textRef.current = text; // Write it to the ref
});
const handleSubmit = useCallback(() => {
const currentText = textRef.current; // Read it from the ref
alert(currentText);
}, [textRef]); // Don't recreate handleSubmit like [text] would do
return (
<>
<input value={text} onChange={e => updateText(e.target.value)} />
<ExpensiveTree onSubmit={handleSubmit} />
</>
);
}
I was so confused:
Instead of useCallback updating the function each time the text changes, this code moves it to textRef in useEffect. But isn't that the same because the callback function doesn't change but you still need 1 step to update the value in useEffect?
Why is textRef in the dependency array? Isn't textRef a reference which doesn't change between renders? We always receive the same reference so wouldn't it be the same as inputting an empty array?
instead of useCallback update the function each time the text change, this code move it to textRef in useEffect but isn't that the same because the callback function doesn't change but you still need 1 step to update the value in useEffect?
With the useRef setup, each render re-runs useEffect but handleSubmit stays the same. In a normal setup, each render would re-run useCallback and handleSubmit would be a new reference to a new function. I can see why on the surface this seems like the same amount of work.
The benefits of the ref approach are what other re-renders happen based on a re-rendering of Form. Each time the value of text changes, the parent Form re-renders and the child input re-renders since it takes text as a prop. ExpensiveTree does not re-render because its prop handleSubmit maintains the same reference. Of course the name ExpensiveTree is designed to tell us that we want to minimize re-rendering of that component since it is computationally expensive.

How to pass the data from useSelector to useState to prevent re render

I want to pass the data from redux to useState but this cause multiple re-renders. It is another variant to do it?
This is my current variant that I`m using
export default function Test(){
const dispatch = useDispatch();
const currentData= useSelector(state => state.currentData);
const [value, setValue] = React.useState(null);
React.useEffect(() => {
dispatch(getData());
},[dispatch]);
React.useEffect(() => {
setValue(currentData.item);
},[data])
return (
<Autocomplete
options={data}
value={value}
/>
)
}
Thanks! And sorry for my English
If you just want to store the new value coming from the store without second render, you can just use useRef, in case you need to keep and read that value in an event handler, let's say.
If you only need the currentData.item part just for the current render, I don't see a reason to use any hooks, but just normal JS variable or directly pass currentData.item to your dependent components. Here's how it may look like with useRef.
export default function Test(){
const dispatch = useDispatch();
const currentData= useSelector(state => state.currentData);
const value = React.useRef(null);
value.current = currentData.item;
React.useEffect(() => {
dispatch(getData());
},[dispatch]);
return (
<Autocomplete
options={data}
value={value.current}
/>
)
}
EDIT:
Based on the sandbox provided, I created a fork. Note that I removed React.StrictMode in index.js. This doubles the number of renders in development only (by design). I also merged the country and location into a single state object, so now calling setState will cause 1 additional render instead of 2 as before. In total 2 renders on init - first, the usual one and second because of setting state in the effect.

React Hook - Use effect on component unmount only, not when dependency updates

I'm trying to build an input component using React Hooks that hits a remote server to save an updated value on component unmount only.
The remote server call is expensive, so I do not want to hit the server every time the input updates.
When I use the cleanup hook in useEffect, I am required to include the input value in the effect dependency array, which makes the remote API call execute on each update of the input value. If I don't include the input value in the effect dependency array, the updated input value is never saved.
Here is a code sandbox that shows the problem and explains the expected outcome: https://codesandbox.io/s/competent-meadow-nzkyv
Is it possible to accomplish this using React hooks? I know it defies parts of the paradigm of hooks, but surely this is a common-enough use case that it should be possible.
You can use a ref to capture the changing value of your text, then you can reference it in another useEffect hook to save the text:
const [text, setText] = useState("");
const textRef = React.useRef(text);
React.useEffect( () => {
textRef.current = text;
}, [text])
React.useEffect( () => {
return () => doSomething(textRef.current)
}, [])
thedude's approach is right. Tweaked it a bit, for this particular usecase as input ref is always same :
function SavedInput() {
const inputEl = useRef(null);
React.useEffect(() => {
return () => {
save(inputEl.current.value);
};
}, []);
return (
<div>
<input ref={inputEl} />
</div>
);
}
By this way you'll avoid re-render as you are not setting any state.

Resources