React state not in sync with the actual state - reactjs

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)

Related

Automatic batching in React 18 is not working?

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

React seems to re-render the wrong component when triggering another one

I have two components in my project.
One is Aapp.jsx
One is Child.jsx
When I called the state set function in Child 1; it is supposed to see mentioning child 1 in the console, but now it is showing child 3. It is weird and why is that?
I guess the reason is "key" in prop, so I added "key" there too. But the problem is still there.
Here is the code:
App:
import React,{useState,useEffect} from 'react';
import {Child} from './Child.jsx'
export function App(props) {
[message,setMessage]=useState('');
[showChild1,setShowChild1]=useState(true);
[showChild2,setShowChild2]=useState(true);
[showChild3,setShowChild3]=useState(true);
[child1data,setChild1data] = useState('child1');
[child2data,setChild2data] = useState('child2');
[child3data,setChild3data] = useState('child3');
useEffect(() => {
console.log('parent was rendered')
})
return (
<div className='App'>
<button onClick={()=>setShowChild1(!showChild1)}>Show child1</button>
{showChild1 && <Child key='1' data={child1data}/>}
<br/>
<br/>
<button onClick={()=>setShowChild2(!showChild2)}>Show child2</button>
{showChild2 && <Child key='2'data={child2data}/>}
<br/>
<br/>
<button onClick={()=>setShowChild3(!showChild3)}>Show child3</button>
<br/>
{showChild3 && <Child key='3' data={child3data}/>}
</div>
);
}
// Log to console
console.log('Hello console')
Child:
import React, {useState, useEffect} from 'react';
export const Child = (props) => {
const {data} = props;
[message,setMessage]=useState('');
useEffect(()=>{
console.log(data)
console.log(message)
})
return <>
<h1>This is {data}</h1>
<input onChange={((e)=>setMessage(e.target.value))}></input>
</>
}
For better illustrate, here is my code
https://playcode.io/940717
This is a tricky one but fortunately comes with a very simple fix.
TL;DR
In Child.jsx change this:
[message, setMessage] = useState('');
to this:
const [message, setMessage] = useState('');
The longer answer
When declaring variables without let, const, or var (in a non-strict environment) Javascript will create an implicit global variable. What this means is that your message and setMessage variables point to the last value they were assigned. I.e. The global variable message will be assigned to the result of the useState call in the last Child component that you render, which in your case is Child 3.
So, when you modify message, Child 3 detects that change and runs the effect for Child 3, not the Child component where the change was actually made.
You can see this in action by changing the Child component to this and examining the output in the console:
[message, setMessage] = useState("");
useEffect(() => {
console.log(data); // Will always log "child 3"
console.log(message);
});
return (
<>
<h1>This is {data}</h1>
<input
onChange={(e) => {
console.log(data); // Will log "child n" where n is the child you expect
setMessage(e.target.value);
}}
></input>
</>
);
You might also be curious as to why, if all of your Child components are referencing the same message variable, you're still able to change the inputs individually. This is because you're input elements are uncontrolled and their state is being managed by the browser natively.
You can see this in action by adding the following to the input element in your Child component:
<input value={message} onChange={e => setMessage(e.target.value)}></input>
What to do in the future
You should always, always, always declare your variables with let or const as it will save you headaches like this in the future. In fact, many modern transpilers such as Babel will throw an error if you don't.
When using uncontrolled inputs you should consider asking yourself why you need to do so. More often than not, a controlled input is what you actually want and in the cases where it's not, consider leaving a comment as to why it's not.
You forgot const in many places:
const [message, setMessage] = useState('');
Without the const keyword message and setMessage (and other stuff) leak to the window global object.
Working example

React - Checking for a value in an uncontrolled input?

I've got a custom Input control, which I'd like to be uncontrolled. I don't care what the user writes or where this is stored. However, I need to know if the input has some value or not, because depending on that it will have an appearance or another (such as a floating label).
Since it's not possible to check purely by CSS if an input has a value or not (because the attribute value does not get updated when the user types and changes its value), the only way to know if it has a value or not is programatically.
If I use useState to keep a local state of its value just to know if it has some (though it keeps being uncontrolled), whenever some external script changes the value of the input using ref (for example, react-hook-forms reset function), it won't get updated because the state won't notice the change, and thus the input will be empty but its state will think it still has a value.
As it's not a possibility, then, I wanted to access and watch for the ref .value changes. However, it doesn't change (or, at least, it doesn't notify the change). If I access the value prop directly, it has the correct value, but it doesn't trigger the useEffect although what I'm using as a dependency is the value and not the ref itself.
const [hasValue, setHasValue] = useState(false);
useEffect(() => {
setHasValue(!!inputRef?.current?.value);
}, [inputRef?.current?.value]);
Here is Codesandbox a sample of what I'm referring.
Is there any way to check the actual value of the input inside my custom component without having to send a value prop from the parent, which is able to react to external changes by
ref, too? So, no matter how the input value changes, it's aware of it.
You could use Inder's suggestion with vanilla javascript or you could trigger your useEffect with afterValue, which gets set onChange.
import React, { useEffect, useRef, useState } from "react";
const Input = () => {
const inputRef = useRef(null);
const [afterValue, setAfterValue] = useState("");
const [hasValue, setHasValue] = useState(false);
useEffect(() => {
setHasValue(!!inputRef?.current?.value);
}, [afterValue]);
return (
<div>
<input
ref={inputRef}
onChange={({ target: { value } }) =>
setTimeout(() => setAfterValue(value), 1000)
}
/>
<br />
<br />
<div>Has value: {hasValue.toString()}</div>
<div>Actual value: {inputRef?.current?.value}</div>
<div>After value: {afterValue}</div>
</div>
);
};
export default Input;

useEffect when onClick

I'm getting stuck about how to use effects together with app logic.
Suppose this component:
import React, { useEffect, useState } from "react";
export default function App() {
const [query, setQuery] = useState('');
useEffect( () => {
fetch('https://www.google.com?q='+query)
.then(response => console.log(response))
}); // depends on what?
return (
<div>
<input onChange={e => setQuery(e.target.value)} value={query} />
<button>Ask Google about {query}</button>
</div>
);
}
I want that:
when (and only) the user clicks the button the fetch is run with the correct query value of the input
if the fetch is still in progress and the user clicks, the fetch is skipped but the effect is fired (meaning: I intentionally not disable the button, I want that the effect function is run, but I put a check inside that function not to execute the fetch).
Problems:
The effect shouldn't fire on mount (it wouldn't make any sense)
The effect shouldn't fire when the query changes, but if I don't put the query variable inside the useEffect dependency array, React complains (react-hooks/exhaustive-deps)
The effect should fire when the user click on the button; I achieved this for example using a fake state isRun, setting onClick={setIsRun(true)}, making the effect depending on [isRun], setting setIsRun(false) at the end of the effect function, and checking if (!isRun) at the beginning of the effect function to prevent that when is set to false from the effect itself it is run again since the state changes. This works, but I find it very verbose and uncomfortable...
The effect should fire if the button is clicked again (with the same query value or not) and the previous fetch has not yet finished without running the fetch: with the previous solution with isRun it wouldn't fire because isRun is already set to 1 so there is no state change; maybe with another state there is a way, but again very verbose and counterintuitive.
Most importantly: the code should be clean and readable without using "tricks"!
How would you write such a component?
It sounds like you shouldn't be using useEffect for this at all. You want this to happen on a user action, not as an effect:
when (and only) the user clicks the button the fetch is run with the correct query value of the input
Remove useEffect and create a function to handle the click:
const handleClick = (e) => {
fetch('https://www.google.com?q='+e.target.value)
.then(response => console.log(response));
};
And pass that function to the component:
<button onClick={handleClick}>Ask Google about {query}</button>
What seems confusing here are these requirements:
if the fetch is still in progress and the user clicks, the fetch is skipped but the effect is fired
The effect should fire if the button is clicked again (with the same query value or not) and the previous fetch has not yet finished without running the fetch
The only thing the function does is execute a fetch. So should that operation happen or not? Your proposed solution of keeping state in a variable (isRun) to determine if it should happen or not should work in this case. I think the problem before was mixing that up with useEffect when all you really want is a function. Add isRun to state and update it accordingly when performing the operation:
const [isRun, setIsRun] = useState(false);
const handleClick = (e) => {
if (isRun) { return; }
setIsRun(true);
fetch('https://www.google.com?q='+e.target.value)
.then(response => {
console.log(response);
setIsRun(false);
});
};
I'm not sure if you want to insist on using useEffect but it does not seem appropriate for this situation. What I could do is call a function on button click.
import React, { useEffect, useState } from "react";
export default function App() {
const [query, setQuery] = useState('');
const handleQuery = (query) => {
fetch('https://www.google.com?q='+query)
.then(response => console.log(response))
}
return (
<div>
<input onChange={e => setQuery(e.target.value)} value={query} />
<button onClick={() => handleQuery(query)}>Ask Google about {query}</button>
</div>
);
}

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