React hooks toggle setState - reactjs

so far I saw two ways to handle toggle
first, set state based on previous state
export default function App() {
const [show, setShow] = useState(false);
const handleClick = () => {
setShow(s => !s);
};
return (
<div>
<div>show: {String(show)}</div>
<button onClick={handleClick}>BTN</button>
</div>
);
}
second, pass the state directly as the argument
export default function App() {
const [show, setShow] = useState(false);
const handleClick = () => {
setShow(!show);
};
return (
<div>
<div>show: {String(show)}</div>
<button onClick={handleClick}>BTN</button>
</div>
);
}
which one is correct? In my understanding the second may not work as set state is async, it may not be able to get the correct state if I click the button several times

The first one is correct:
setShow(s => !s);
Although the second one works in this case it is not correct:
// bad code
setShow(!show);
Why?
Setting state is asynchronous - but what that really means is that setting state is deferred until re-render.
An example:
const [myNum, setMyNum] = useState(0)
const handleUpdate = () => {
setMyNum(myNum + 1)
}
If we call the handleUpdate function, the new value of myNum is incremented by 1. That works correctly, but what about this:
const [myNum, setMyNum] = useState(0)
const handleUpdate = () => {
setMyNum(myNum + 1)
setMyNum(myNum + 1)
}
You may think that myNum is now 2, but it's not, it's still 1.
This happens because the value of myNum does not change until re-render, so you're essentially writing this:
const handleUpdate = () => {
setMyNum(0 + 1)
setMyNum(0 + 1)
}
That is why you should always provide a function to set state if you depend on the previous value.
This works correctly:
const handleUpdate = () => {
setMyNum(p => p + 1)
setMyNum(p => p + 1)
}
myNum is now 2.
Even if you're only setting the value once, you should maintain good practice. When your app gets complex and several different actions may set the state at any time, you don't want to be caught out by something you wrote months ago that you never thought was going to matter.

Related

React, useCallback re-call again even if dependency did not change

I'm using useCallback to memoize a function, I wrote 2 functions, formatCounter and formatCounter2 and added useCallback to the functions with dependencies of counter and counter2.
When onClick function is called, the counter variable has changed but formatCounter2 called, but why? As I understand it needs to be called only when the dependency changes (counter2) and it does not change.
function App() {
const [counter, setCounter] = React.useState(0);
const [counter2, setCounter2] = React.useState(0);
const onClick = React.useCallback(()=>{
setCounter(prevState => ++prevState)
},[]);
const onClickSecond = React.useCallback(()=>{
setCounter2(prevState => ++prevState)
},[]);
const formatCounter = React.useCallback((counterVal)=> {
console.log('formatCounter Called')
return `The counter value is ${counterVal}`;
},[counter])
const formatCounter2 = React.useCallback((counterVal2)=> {
console.log('formatCounterTwo Called')
return `The counter value2 is ${counterVal2}`;
},[counter2])
const objMemo = React.useMemo(()=> {
console.log('obj memo is')
return {
a:counter>2?'counter is bigger than 2': 'counter is less than 2'
}
},[counter])
return (
<div className="App">
<div>{formatCounter(counter)}</div>
<div>{formatCounter2(counter2)}</div>
<button onClick={onClick}>
Increment
</button>
<button onClick={onClickSecond}>
Increment2
</button>
<p>{objMemo.a}</p>
</div>
);
}
link to code
useCallback memoizes the function reference. But you're calling the format functions (both) in the render method. So what you're actually seeing is the inner function being called.
Maybe out of curiosity you can check the reference to the function you're memoizing. There you will see that the reference is not changing.
To check this I usually do:
useEffect(() => {
console.log('formatCounter reference changed!', formatCounter);
}, [formatCounter]);
You will see that it logs for when counter changes but not for when counter2 changes.

State is always one click late

I have a problem with my code, basically setRevalue is always one click late.
const Exchanger = () => {
const [revalue, setRevalue] = useState('null')
const [valueOfInput, setValueOfInput] = useState('');
const [valueOfSelect, setValueOfSelect] = useState('');
const handleClick = () => {
switch (valueOfSelect) {
case 'option_1':
axios.get('http://api.nbp.pl/api/exchangerates/rates/a/chf/?format=json')
.then((response) => {
setRevalue(response.data.rates[0].mid)
console.log(revalue);
})
break;
const handleInputChange = (event) => {
setValueOfInput(event.target.value);
}
const handleSelectChange = (event) => {
setValueOfSelect(event.target.value);
}
return(
<>
<Input
labelText= 'Amount'
onChange= { handleInputChange }
value = { valueOfInput }
/>
<Select selectValue={ valueOfSelect } onSelectChange={ handleSelectChange } />
<Button onClick={handleClick}>Klik!</Button>
</>
)
On the first click it returns a null which it was supposed to be before the click.
Any help would be much appreciated.
Hey so you have a clousure issue there
In JavaScript, closures are created every time a function is created, at function creation time.
At the time the .then() function is created, revalue has the value of the last render, That's why even after updating the state with setRevalue (and thus triggering a rerender), you get the "old value" (the one corresponding to the render in which the .then function was created).
I don't know exactly your use case, but seems like you can achieve whatever you are trying to achieve by doing:
const revalue = response.data.rates[0].mid
setRevalue(revalue)
console.log(revalue);
if you want to access to revalue right after setting it you have to use useEffect with revalue as a parameter , it would be like this
useEffect(()=>{
console.log(revalue);
},[revalue])
now every time revalue change you will have current result

React, useEffect only when 2 elements of dependency array are updated

I have a React table component where both Column component and a button component rendered inside a column, triggers some function. Example of the table:
<Table>
<Column onCellClick={handleCell}>
<button onClick={handleButton} />
</Column>
</Table>
Those 2 handle functions are called at the same click, and they trigger some useStates:
const [cell, setCell] = useState(false);
const [buttonValue, setButtonValue] = useState(false);
const handleCell = (value) => setCell(value)
const handleButton = (value) => setButtonValue(value)
SO I have a useEffect that must trigger some code ONLY when both cell and buttonValue are updates. Currently I have something like:
useEffect(() => {
if (cell && buttonValue) {
// some code
setAnotherState(etc)
}
}, [cell, buttonValue]);
My problem is if I click very quickly or in random scenarios, the setAnotherState is called before the buttonValue or cell are actually updated. With that if inside the useEffect, it will work correctly only the very first time that I actually update both values, because they both are initialized as false, but then, with many clicks, there are some outdated set states.
Any hint? how could I ensure that both states actually update before executing the code inside the if?
I can't remove any of that 2 onClick or onCellClick, they both have different and specific values for their components, so I need them both.
Here's an idea using useRef to store the collective state as an integer value you can test against.
Cell adds 2 to the state (first taking a modulus of 2 to remove existing 2 values), Button adds 1 (first doing a bit-shift and x2 to eliminate the first bit). When the stateRef is 3, the effect knows both have been changed, sets the other state, and resets the stateRef to 0.
const [cell, setCell] = useState(false);
const [buttonValue, setButtonValue] = useState(false);
const stateRef = useRef(0);
const handleCell = (value) => {
setCell(value);
stateRef.current = stateRef.current % 2 + 2;
};
const handleButton = (value) => {
setButtonValue(value);
stateRef.current = (stateRef.current >> 1) * 2 + 1;
};
Then
useEffect(() => {
if (stateRef.current == 3) {
// some code
setAnotherState(etc)
stateRef.current = 0;
}
}, [stateRef.current]);
You could do something like this:
const [cell, setCell] = useState(null);
// it stores the updated status for cell
const [cellStatus, setCellStatus] = useState(false);
const [buttonValue, setButtonValue] = useState(null);
// it stores the updated status for buttonValue
const [buttonValueStatus, setButtonValueStatus] = useState(false);
const handleCell = (value) => {
setCell(value)
setCellStatus(true) // it carried a updated event
}
const handleButton = (value) => {
setButtonValue(value)
setButtonValueStatus(true) // it carried a updated event
}
useEffect(() => {
// !!(null) === false
if (!!cellStatus && !!buttonValueStatus) {
// some code
setAnotherState(etc)
// here you reset their status since they were already updated and used
// now they are not new values but old ones
setCellStatus(false)
setButtonValueStatus(false)
}
}, [cell, buttonValue]);
Thus, the basic idea is to keep track of the values in one variable and the update status in another one.

React state not updating in click event with two functions, but updating with one function

This one has turned out to be a head scratcher for a while now...
I have a react component that updates state on a click event. The state is a simple boolean so I'm using a ternary operator to toggle state.
This works however as soon as I add a second function to the click event state no longer updates. Any ideas why this is happening and what I'm doing wrong?
Working code...
export default function Activity(props) {
const [selected, setSelected] = useState(false);
const selectActivity = () => {
selected ? setSelected(false) : setSelected(true);
return null;
};
const clickHandler = (e) => {
e.preventDefault();
selectActivity();
};
return (
<div
onClick={(e) => clickHandler(e)}
className={`visit card unassigned ${selected ? 'selected' : null}`}
>
//... some content here
</div>
);
}
State not updating...
export default function Activity(props) {
const [selected, setSelected] = useState(false);
const selectActivity = () => {
selected ? setSelected(false) : setSelected(true);
return null;
};
const clickHandler = (e) => {
e.preventDefault();
selectActivity();
props.collectVisitsForShift(
props.day,
props.startTime,
props.endTime,
props.customer
);
};
return (
<div
onClick={(e) => clickHandler(e)}
className={`visit card unassigned ${selected ? 'selected' : null}`}
>
//... some content here
</div>
);
}
I went for a walk and figured this one out. I'm changing state in the parent component from the same onClick event, which means the child component re-renders and gets its default state of 'false'.
I removed the state change from the parent and it works.
Thanks to Andrei for pointing me towards useCallback!
I loaded your code in a CodeSandbox environment and experienced no problems with the state getting updated. But I don't have access to your collectVisitsForShift function, so I couldn't fully reproduce your code.
However, the way you're toggling the state variable doesn't respect the official guidelines, specifically:
If the next state depends on the current state, we recommend using the updater function form
Here's what I ended up with in the function body (before returning JSX):
const [selected, setSelected] = useState(false);
// - we make use of useCallback so toggleSelected
// doesn't get re-defined on every re-render.
// - setSelected receives a function that negates the previous value
const toggleSelected = useCallback(() => setSelected(prev => !prev), []);
const clickHandler = (e) => {
e.preventDefault();
toggleSelected();
props.collectVisitsForShift(
props.day,
props.startTime,
props.endTime,
props.customer
);
};
The documentation for useCallback.

What is the correct way to use react hook useState update function?

Considering the following declaration:
const [stateObject, setObjectState] = useState({
firstKey: '',
secondKey: '',
});
Are the following snippets both corrects ?
A)
setObjectState((prevState) => ({
...prevState,
secondKey: 'value',
}));
B)
setObjectState({
...stateObject,
secondKey: 'value',
}));
I am sure that A) is correct, but is it necessary ? B) seems ok, but as setObjectState is an asynchronous function, stateObject might not have the most recent value.
One useful thing about case of A that I have found is that you can use this method to update state from child components while only passing down a single prop for setObjectState. For example, say you have parent component with state you would like to update from the child component.
Parent Component:
import React, {useState} from 'react';
import ChildComponent from './ChildComponent';
export const ParentComponent = () => {
const [parentState, setParentState] = useState({
otherValue: null,
pressed: false,
});
return (
<ChildComponent setParentState={setParentState} />
)
}
Child Component:
import React from 'react';
export const ChildComponent = (props) => {
const callback = () => {
props.setParentState((prevState) => ({
...prevState
pressed: true,
}))
}
return (
<button onClick={callback}>test button<button>
)
}
When the button is pressed, you should expect to see that the state has been updated while also keeping its initial values. As for the difference between the two, there isn't much as they both accomplish the same thing.
A will always give you the updated value. B could be correct but might not. Let me give an example:
const Example = props => {
const [counter, setCounter] = useState(0);
useEffect(() => {
// 0 + 1
// In this first case the passed value would be the same as using the callback.
// This is because in this cycle nothing has updated counter before this point.
setCounter(counter + 1);
// 1 + 1
// Thanks to the callback we can get the current value
// which after the previous iexample is 1.
setCounter(latest_value => latest_value + 1);
// 0 + 1
// In this case the value will be undesired as it is using the initial
// counter value which was 0.
setCounter(counter + 1);
}, []);
return null;
};
When the new value depends on the updated one use the callback, otherwise you can simply pass the new value.
const Example = props => {
const [hero, setHero] = useState('Spiderman');
useEffect(() => {
// Fine to set the value directly as
// the new value does not depend on the previous one.
setHero('Batman');
// Using the callback here is not necessary.
setHero(previous_hero => 'Superman');
}, []);
return null;
};
Also in the example you are giving it would probably be better to use two different states:
const [firstKey, setFirstKey] = useState("");
const [secondKey, setSecondKey] = useState("");

Resources