React setState doesn't update boolean variable - reactjs

I created a state variable using
const [drawing, setDrawing] = useState(false)
Then I've this button which should update the value of drawing
<button onClick={() => toggleDrawing}>Toggle Drawing</button>
The toggleDrawing function is:
const toggleDrawing = () => {
setDrawing(true)
}
but when I press the button and console.log the value of drawing id doesn't update, it's always a step behind. it start with a value of false, after one click it remains false and after the second click it switches to true.

const toggleDrawing = () => {
setDrawing((oldValue) => !oldValue)
}
if you want to toggle state in React you always want to refer to the previous state

const [drawing, setDrawing] = useState(false)
Case:1 If you want a toggle true=>false and false=>true
const toggleDrawing = () => {
setDrawing(!drawing);
}
<button onClick={toggleDrawing}>Toggle Drawing</button>
Case:2 If you just want to set it to true
const toggleDrawing = () => {
setDrawing(true);
}
<button onClick={toggleDrawing}>Toggle Drawing</button>
Sandbox https://codesandbox.io/s/vigilant-meadow-thdni?file=/src/App.js
Note => console.log(drawing) in toggleDrawing function will return false at first then true reason is state is not updated

The Problem
You're just returning the toggleDrawing function without executing it
Solution
Execute the function when the click event occurs like this
<button onClick={() => toggleDrawing()}>Toggle Drawing</button>
Or pass the toggleDrawing to onClick as the actual function like
<button onClick={toggleDrawing}>Toggle Drawing</button>

Related

Value isn't be updated async in React useState (React)

I want to change State with child elements in React. However, when I click once, it is not immediately updated. Click twice, it shows the correct answer.
How to update async?
export default function Example() {
const onClick = async () => {
console.log('a', test)
// should be 'b', but console log 'a'
}
const [test, setTest] = useState('a')
return (
<ClickExample setTest={setTest} onClick={onClick} />
)
}
export default function ClickExample() {
const next = useCallback(
(alphabet: string) => {
setTest(alphabet)
onClick()
},
[onClick, setTest],
)
return <SelectButton onClick={() => next('b')} />
}
You can receive the value to be updated as an argument from the onClick callback. It'll be something like this:
export default function Example() {
const [test, setTest] = useState('a')
const handleClick = (newValue) => {
setTest(newValue);
}
return (
<ClickExample onClick={handleClick} />
)
}
export default function ClickExample({ onClick }) {
return <SelectButton onClick={() => onClick('b')} />
}
NOTE: You should avoid using useCallback() when it is not necessary. Read more over the web but this article from Kent C. Dodds is a good start. As a rule of thumb: Never use useCallback()/useMemo() unless you REALLY want to improve performance after needing that improvement.
In the first render, the value of test is equal to'a'. So when the console.log is executed, it has already captured 'a' as the value of test state. (See closures and stale closures).
One way to fix this would be to create a handleClick function in the parent component which receives the new value of test as its input and set the state and log the new value(which will be updated in the next render) using its argument.
// ClickExample
const handleClick = (alphabet) => {
setTest(alphabet);
console.log('a', alphabet);
};
codesandbox

How do you cleanly convert React setState callback to useEffect hooks?

I can create a class component with 2 buttons, "A" and "B". Button A should set a state variable and then take some action. Button B should also set the same state variable and then take a different action. They key thing that this hinges on is the second argument to setState, which is the callback function for what action to take after the state has been set.
When I try to write this as a function component, I run into an issue. I move the callbacks into a hook (or multiple hooks), but I have no way to know which button was clicked. Furthermore, the hook does not trigger every time I click the button. The hook only triggers when the button results in a real state change (e.g. changing 5 to 5 skips the effect).
Here is a simple example:
import React from 'react';
import './styles.css';
// actionA and actionB represent things that I want to do when the
// buttons are clicked but after the corresponding state is set
const actionA = () => {
console.log('button A thing');
};
const actionB = () => {
console.log('button B thing');
};
// render 2 components that should behave the same way
const App = () => {
return (
<>
<ClassVersion />
<FunctionVersion />
</>
);
};
// the class version uses this.state
class ClassVersion extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
};
}
render = () => {
return (
<div className='box'>
Class Version
<br />
<button
type='button'
onClick={() => {
this.setState({ x: 1 }, () => {
// here I can do something SPECIFIC TO BUTTON A
// and it will happen AFTER THE STATE CHANGE
actionA();
});
// can't call actionA here because the
// state change has not yet occurred
}}
>
A
</button>
<button
type='button'
onClick={() => {
this.setState({ x: 2 }, () => {
// here I can do something SPECIFIC TO BUTTON B
// and it will happen AFTER THE STATE CHANGE
actionB();
});
// can't call actionB here because the
// state change has not yet occurred
}}
>
B
</button>
State: {this.state.x}
</div>
);
};
}
// the function version uses the useState hook
const FunctionVersion = () => {
const [x, setX] = React.useState(0);
React.useEffect(() => {
// the equivalent "set state callback" does not allow me to
// differentiate WHY X CHANGED. Was it because button A was
// clicked or was it because button B was clicked?
if (/* the effect was triggered by button A */ true) {
actionA();
}
if (/* the effect was triggered by button B */ true) {
actionB();
}
// furthermore, the effect is only called when the state CHANGES,
// so if the button click does not result in a state change, then
// the EFFECT (AND THUS THE CALLBACK) IS SKIPPED
}, [x]);
return (
<div className='box'>
Function Version
<br />
<button
type='button'
onClick={() => {
// change the state and trigger the effect
setX(1);
// can't call actionA here because the
// state change has not yet occurred
}}
>
A
</button>
<button
type='button'
onClick={() => {
// change the state and trigger the effect
setX(2);
// can't call actionB here because the
// state change has not yet occurred
}}
>
B
</button>
State: {x}
</div>
);
};
export default App;
I came up with a couple ideas:
Change the type of x from number to {value: number; who: string}. Change calls from setX(1) to setX({value: 1, who: 'A'}). This will allow the effect to know who triggered it. It will also allow the effect to run every time the button is clicked because x is actually getting set to a new object each time.
Change the type of x from number to {value: number; callback: () => void}. Change calls from setX(1) to setX({value: 1, callback: actionA}). Same as above, but slightly different. The effect would say if(x.callback) { x.callback() }.
Both these ideas seem to work, but the problem is I don't want to keep adding an extra tag to my state variables just to keep track of which thing triggered them.
Is my idea the correct solution, or is there a better way to do this that doesn't involve "tagging" my state variables?
How about this:
const [a, setA] = React.useState(0);
const [b, setB] = React.useState(0);
React.useEffect(() => {
actionA();
}, [a]);
React.useEffect(() => {
actionB();
}, [b]);
onClick={() => {
setX(1);
setA(a => a + 1);
}}
onClick={() => {
setX(2);
setB(b => b + 1);
}}
Let's say you put actionA and actionB in the corresponding button A/B onClick(). Given that you WILL be updating state based on the action of this onClick, you could directly call the action functions during the onClick if you pass the new state data to the functions.
onClick={() => {
actionA(newStateData);
setX(newStateData);
}}
If you don't want to do this, you could use:
your solution of adding an identifier to the state value would be fine!
Create separate state values for A and B

React: How to add a button click handler to reveal text?

I want create a button click handler to reveal the answer text "Animal". So far I have <button onClick={this.revealAnswer}>Reveal answer</button> and the handler
revealAnswer = () => { };
What information should I put in the handler?
Use the useState hook.
const [reveal, setReveal] = useState(false);
const revealAnswer = () => {
setReveal(reveal => !reveal)
}
...
return (
...
{reveal && text}
...
)
You can add a state variable (boolean type) to the component, something like const [revealAnswer, setRevealAnswer] = useState(false). Here, 'false' being the default value.
In the handler, you can then update the state every time the button gets clicked.
const revealAnswer = () => {
setRevealAnswer(!revealAnswer);
};
And in your JSX you should have condition based on this variable revealAnswer. Eg:
...
{ revealAnswer && textValue }
Hope that helps!

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.

why child state value is not updating in parent callback function at first time?

why child state value is not updating in parent callback function at first time? i want to make my input-Field disable based on state.
Sandbox with full example: https://z3wu6.csb.app/
Issue
PageHeader
Initial state is true, when you click the the button the editViewHandler is called. It toggles the edit state of this component but then calls the editCallback callback with the current value of edit which is initially true. You're sending the non-updated value to the callback! (you set it to true again in UserProfile) You can fix this by also inverting the edit value sent to editCallback.
const [edit, setEditView] = useState(true);
const editViewHandler = () => {
setEditView(!edit);
editCallback(!edit); // <-- send the same value you update state to
};
I see you've also duplicated this edit state in UserProfile. You shouldn't duplicate state. You want a single source of truth.
You already pass editCallback from UserProfile so just attach that as the callback to the button.
Suggestion Solution
Toggle the value in the source callback in UserProfile
const UserProfile = () => {
const [edit, setEdit] = useState(true);
const editCallback = () => setEdit(edit => !edit);
return (
<>
<PageHeader
button
editCallback={editCallback}
title="User Profile"
subtitle="Overview"
/>
<UserAccountDetails edit={edit} />
</>
);
};
And attach to button's onClick handler
const PageHeader = ({ title, subtitle, button, editCallback }) => (
<div className="page-header py-4 withBtn d-flex align-items-center">
{button ? <Button onClick={editCallback}>Edit Detail</Button> : null}
</div>
);

Resources