React state updating but rendering late - javascript

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?

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
)

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

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.

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

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 (...)
}

Related

Why is the value on display not in sync with value logged to console?

Can anyone tell me why at button click, the value outputted to the console is always one unit smaller than displayed on the screen?
The values are not in sync as expected.
Example below in React
In Child:
import React, {useState } from "react";
export const ChildComp = ({getNumProps}) => {
const [num, setNum] = useState(0);
const onPlusClick = () => {
if (num< 12) {
setNum(num + 1);// num does not increase immediately after this line, except when focus reenters here on second method call
}
getNumProps(num);
}
return(
<div>
<button onClick={onPlusClick}>
Click to increment
</button>
{num}
</div>
);
}
In parent
import { ChildComp } from "./ChildComp"
export const ParentComp = () => {
const getNum= (num) => {
console.log(num);
}
return (<ChildComp getNumProps={getNum}/>)
}
The page initially shows 0
When I click once the number increments to 1, but console displays 0
When I click once the number increments to 2, but console displays 1
I should see in the console the same as the page display
Appreciate if you can leave a commen on how the question can be improved.
This is a child to parent communication example. Also, any objections about standards used, please let me know.
Thanks.
Update: I notice the values would be in sync if
instead of getNumProps(num);
I did getNumProps(num + 1); but that doesn't change the fact that previously on this line
setNum(num + 1);, as already pointed out in the comment, num does not increase immediately after this line, except when focus reenters here on second method call. Not sure why.
The prop function getNumProps is a side effect, and should be put into a hook, instead of inside of that onPlusClick function.
Instead, do this:
useEffect(() => {
getNumProp(num);
}, [num]);
Alternatively, to avoid the error: "React Hook useEffect has a missing dependency: 'getNumProps'. See this doc on using the useCallback hook
const callback = useCallback(() => {
getNumProp(num);
}, [num]);
function onPlusClick(...) {
...
callback();
}
The change to the state of num will cause a re-render of the child component, not the parent.

Can't use updated state in function in React

I am currently using Agents.tsx as a parent function and StickyFilter.tsx as the child. I am setting a state with an initial value of an empty string in Agents.tsx like so:
const Agents = (props: AgentsProps): ReactElement => {
const { agentsMode, recruitClient, recruitGlobals, setRecruitClient, setShowLoading } = props;
const [savedSearchName, setSavedSearchName] = useState(''); // set state here
I then am passing that setSavedSearchName as a prop to StickyFilter.tsx:
const StickyFilter = (props: StickyFilterProps): ReactElement => {
const { otherProps,
setSavedSearchName
} = props;
later down in StickFilter.tsx I am calling on that state function like so:
const setSavedSearch = (savedSearch: RecruitAgentSearch): void => {
let revisedSearches: AgentSearch[] = [];
// Redacted code -- bunch of logic
// Value gets set here
setSavedSearchName(revisedSearchDetails.criteria.searchName || '');
};
Once that value gets set in StickyFilter.tsx I am able to have it console log in the parent component just fine. I have a table that renders and once I click on a certain item it fires off the saveNewNote function, but for some reason the function is still pulling in the empty string but whenever I click on the item a SECOND time, it'll pull in the right value. What am I missing here? I tried inputting a useEffect hook like so:
useEffect(() => {
console.log(savedSearchName)
}, [savedSearchName]);
but it will not console log the correct value until I do the action on the table a second time.
Any advice is appreciated.
Edit: Here is the saveNewNote function in Agents.tsx
const saveNewNote = async (agent: Agent, agentsMode: AgentsMode): Promise<void> => {
// just trying to console log here after the state is set
console.log(savedSearchName); // returning empty string
console.log(recruitStoreApi.savedSearchName);
console.log(stateApi);
// Simple logic redacted
}
I ended up changing the data structure that injects into the table. I could not find a way to get the latest state.

React listen to 1st state change only

I am using useEffect in react to listen to redux(easy-peasy) state change, but I want to listen to 1st value change only.
Because when my page loads the state has a default value and then API call is made and hence data changes but the API is a polling API, hence it keeps getting the data again and again in a short interval of time. But one of my requirement is to listen only to the 1st API data.
This is what I tried:
1st Approach with empty dependency
useEffect(() => {
// my code
},[])
In this case, I get the default value of my state and not the 1st API response.
2nd Approach with state property in the dependency
useEffect(() => {
// my code
},[myState])
In this case, I keep getting the updated response from the API
both of these approaches didn't work for me. So please suggest a better solution.
You can do so using a ref variable and comparing the state with initial state (which could be null, undefined, empty object depending on your implementation):
const hasRun = useRef(false)
useEffect(() => {
if (!hasRun.current && myState !== initialState) {
hasRun.current = true
// run my code
}
},[myState])
A ref variable won't participate in re-rendering.
What I usually do with this is to have a "previous" state.
You can use this hook for to do that one:
const usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]); // only re-run if value changes
// return previous value (happens before update in useEffect)
return ref.current;
}
You can then do:
// I usually create a distinct initial state
const [var, setVar] = useState(null);
const prevVar = usePrevious(var);
useEffect(() => {
if (var !== prevVar && var !== null) {
// logic here
}
}, [prevVar, var]);
Yers you can simplify this by not using the usePrevious hook, but the hook is quite handy and can be used to check the previous value and the current one.

Click handler, in react, doesn't return latest change in state when it executes a 2nd time

This takes place in a functional component:
import {useEffect} from 'react';
let [clickedOnPiece, setClickedOnPiece] = useState(false);
let [testRender, setTestRender] = useState(false);
useEffect(() => {
testRenderFunction();
}, [])
function testRenderFunction() {
let el = <div onClick={onClickHandler}>Click me</div>;
setTestRender(el);
}
function onClickHandler() {
if (clickedOnPiece) {
console.log("already clicked")
return
}
console.log(clickedOnPiece); //returns false the 1st & 2nd time.
setClickedOnPiece("clicked");
}
return (
<>
{testRender}
</>
)
When I click on div for the first time, I wait until setClickedOnPiece("clicked") successfully updates clickedOnPiece to "clicked". (I check this with React Developer Tools).
When I click div the 2nd time, it doesn't log the new change in state. It still logs clickedOnPiece as false. Why is this?
Okey this problem is because useState is asyncronus. u can read more about this useState set method not reflecting change immediately.
I think the solution is add useEffect like this.
useEffect( () => {
console.log(clickOnPiece);
}
, [clickOnPiece])
If you want to toggle the state, you could do something like this:
let [clickedOnPiece, setClickedOnPiece] = useState(false);
const onClickHandler = () => {
// set the value of clickedOnPiece to the opposite of what it was
// i.e. if it was 'true', set it to 'false'.
setClickedOnPiece(!clickedOnPiece);
console.log(clickedOnPiece);
}
// call the onClickHandler on click
<div onClick={()=>onClickHandler()}>Click me</div>
Looks like you are toggling
let [clickedOnPiece, setClickedOnPiece] = useState(false);
const onClickHandler = () => {
console.log(clickedOnPiece);
setClickedOnPiece(!clickedOnPiece);
}
console.log(clickedOnPiece);
<div onClick={onClickHandler}>Click me</div>
After setting state, don't console immediately because state is an asynchronous.
onClickHandler references the old, previous variable, clickedOnPiece. I believe this is because onClickHandler is not defined in the return statement part of the functional component which would have allowed it a new onClickHandler body to be created each time. Instead, we have the old onClickHandler continually referencing the old clickedOnPiece.
This problem is known as 'stale closures' - a concept I found discussed well at the bottom of this article

why it is not showing update value on button click from state?

could you please tell me how to get updated value from state.here is my code
https://codesandbox.io/s/cool-ives-0t3yk
my initial state
const initialState = {
userDetail: {}
};
I enter 10 digit number on input field and press enter and update the user detail like this
const onSubmit = async values => {
if (values.mobile.length === 10) {
setUserDetail({ msdin: values.mobile });
console.log(userDetail);
}
};
setUserDetail({ msdin: values.mobile }); here I am updating my userdetail .
and try to console the update value like this
console.log(userDetail); .it is showing currently undefined.but expected output is {msdin:'9999999999'} (or whatever it is type in input field)
The problem is that you are using hooks and it's not synchronised, it's async. Therefore, accessing the detail immediately after setting the value will not be possible. If you want to access the data there, you will have to use values.mobile
The state will keep the last value until the next render is called.
You can see this information on react-hooks document
During subsequent re-renders, the first value returned by useState will always be the most recent state after applying updates.
So, the code should look like:
const onSubmit = async values => {
if (values.mobile.length === 10) {
const newUserDetailState = { msdin: values.mobile }
setUserDetail(newUserDetailState);
// do your stuffs with the newUserDetailState instead of userDetail
console.log(newUserDetailState);
}
};
The state setter setUserDetail is async, that means that the new state value won't be available immediately.
To see if the state update use useEffect like this :
useEffect(() => {
console.log('useEffect -> UserDetail : ', userDetail);
}, [userDetail]);

Categories