I'm learning React.js and reading through react.js official docs. There is a example presented by the official docs, that I have questions for:
original code:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
my question is :
in the handleClick method, why can't this.setState be written as(without the arrow function):
handleClick() {
this.setState({
isToggleOn: !prevState.isToggleOn
});
}
The reason why you can't do that is that prevState will be undefined and you can't access property key on the undefined object. Whenever you want to change logic based on your previous state you should be using callback function so you can avoid unnecessary direct mutations of state.
In this case both will work. But sometimes you get unexpected output when changing the state on the previous state value. For more info go to
React's setState method with prevState argument
You can understand there.
Related
In React Docs, handling events article: how state is being updated to the opposite boolean value, I have a question.
Why do we need to add .isToggleOn after isToggleOn: !prevState
Why can't I just simply write isToggleOn: !prevState?
prevState is {isToggleOn: true}. So !prevState should be {isToggleOn: false}. Am I right?
It confuses me, because it sounds like {property: opposite of the boolean.property}.
I know what prevState is and I know .setState() is updating the state.
Please help me better understand this. Thank you so much in advance!
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
prevState is an object, so you cannot change the properties inside it by using "!" on the whole object. What you need is to change a value within this object, which has key "isToggleOn". Thereby, by using
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
you access this value by key "isToggleOn" and change it to the opposite
In react, the state object is a collection of values, not just a single value. In order to update a specific piece of your state, you need to pass the key of that specific state value in order for it to know what to change.
For example, your state value could be something like this:
this.state = {
isToggleOn: true,
otherValue: false
}
And when you're updating, only the value for the specific key you pass is updated. So if you were to run
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
only the isToggleOn value would change and the otherValue field would remain as false.
i'm facing an issue with react's method (setState), hope you can help.
I have a handleChange method using dynamic keys to 'persist' data in the state.. i looks like this:
handleChange = (event, group) => {
event.persist(); //Used to avoid event recycling.
this.setState(
prevState => ({
...prevState,
[group]: { ...prevState[group], [event.target.name]: event.target.value }
}),
() => console.log("state", this.state)
);
};
this method works pretty well when theres just one 'instance' of my custom component using the mentioned handleChange method. The problem began when i wanted to have several components using that method, because when called, its overriding the prevState value. For example:
Initial state: {mockedValue:'Im here to stay'}
then i call handleChange for group 'alpha', to add to this values {name:a},
Next state: {alpha:{name:a},mockedValue:'Im here to stay'}
then i call handleChange for group 'beta', to add to this values {otherName:b},
expected state: {alpha:{name:a}, beta:{otherName:b},mockedValue:'Im here to stay'}
Next state : beta:{otherName:b},mockedValue:'Im here to stay'}
Not sure why this is happening, perhaps i'm misunderstanding some concept, the fact is that i don't have idea why this is not working as expect, (perhaps it's because computed name value, but not sure..) Do you have any idea how to solve this?
Thanks for reading! :)
Update
Code in sandbox: https://codesandbox.io/s/v3ok1jx175
Update2: SOLVED
Thanks for your support Thollen and DevSerkan, i really appreciate it.
The problem was that i had the handleChange event at the wrong level... it means that i was defining the handleChange method inside the child Componet, for instance:
class Parent extends React.Component {
render(){
return(
<div>
<Child/>
<Child/>
<Child/>
</div>
);
}
}
so there was just one 'instance' of handleChange method shared by all the 'instances' , it's a wrong approach. to solve this, i modified Parent like this:
class Parent extends React.Component {
handleChange(){
//updateState Operations here...
}
render(){
return(
<div>
<Child handleChange = {this.handleChange}/>
<Child handleChange = {this.handleChange}/>
<Child handleChange = {this.handleChange}/>
</div>
);
}
}
in this way, i removed the responsibility of handling change from the 'top level child' to the parent, where the child components were being used.
I have written the following sample counter app using react.js. onClick fires the handleAddOne but the value of count never gets incremented.
class Counter extends React.Component {
constructor(props){
super(props);
this.handleAddOne = this.handleAddOne.bind(this);
this.state = {
count : 0
};
}
handleAddOne() {
console.log('handleAddOne');
this.state.count++;
}
render() {
return (
<div>
<h1>Count:{this.state.count}</h1>
<button onClick={this.handleAddOne}>+1</button>
</div>
);
}
}
ReactDOM.render(<Counter />, document.getElementById('app'));
In react, state is an immutable object, meaning you are supposed to set state values using only setStste function and not directly.
<button onClick={()=>{this.setState({count:this.state.count++})
}}>
+1
</button>
As the other answers suggest, you should be using setState to trigger the state change and rerender.
However, when using prevState it is also recommended that you use the functional version of setState
handleAddOne() {
console.log('handleAddOne');
this.setState(prevState=>({
count: prevState.count+1
}));
}
Documentation: https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly
I advise you to read more about Reactjs from https://reactjs.org/docs/hello-world.html
handleAddOne() {
console.log('handleAddOne');
this.setState({count: this.state.count++})
}
So have an input field:
<input
id={itemId}
onChange={handleChange}
type="number"
value={packed}
/>
And here is my onChange function:
handleChange(e) {
const { items, onUpdateQuantity } = this.props;
const updateItem = items.filter((item) =>
item.itemId === e.target.id,
);
const itemQuantity = toNumber(e.target.value);
updateItem.total += itemQuantity;
onUpdateQuantity(e.target.id, itemQuantity);
}
So why is React still complaining about an onChange handler not being defined when it already is? I don't want to add a defaultValue prop, as that causes bugs in my app. Any ideas?
That is coming because your value is not changing anywhere. As you can see from docs for controlled components, the value of the input is this.state.value and the onChange method changes the value inside the input by changing this.state.value.
As far as I can see, when you input a value inside the input (<input/>) element, the value of that element is not changing. It is always whatever the value of packed is. That is why you're getting the error.
Make sure you bind you function in the constructor
class bla extends Component {
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
}
}
or if using stage 0 to have you have your function in this way.
handleChange = () => {}
and if not
handleChange () {
return (e) => {};
}
Also, if your using a class component you should call your handleChange with this.handleChange if your passing it to functional component then what you have should be fine
https://reactjs.org/docs/handling-events.html
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
This is not React-specific behavior; it is a part of how functions work in JavaScript. Generally, if you refer to a method without () after it, such as onClick={this.handleClick}, you should bind that method.
Don't forget to use this.handleChange in you JSX
Example:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
What's wrong with my react method below
toggleReply = () => {
this.setState(prevState => ({ reply: !prevState.reply }))
}
I know I can dothis.setState({reply: !this.state.reply})but above code will also work, but it didn't, any clue?
Your setState works, as per the docs - try this snippet below. This is assuming you Component class is correctly defined and your toggleReply handler is either bound to this during construction, or you're using an arrow function to call it.
class Thing extends React.Component {
constructor(props) {
super(props);
this.state = { reply: false };
this.toggleReply = this.toggleReply.bind(this);
}
toggleReply() {
this.setState(prevState => ({ reply: !prevState.reply }))
}
render() {
return (
<div>
<button onClick={this.toggleReply}>Toggle Reply</button>
<span>{` ${this.state.reply}`}</span>
</div>
);
}
}
ReactDOM.render(
<Thing />,
document.getElementById('root')
);
<script src=" https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="root"></div>
You need to make sure ES7 class properties are enabled:
https://babeljs.io/docs/plugins/transform-class-properties/
That is what allows you to use the arrow function inside of your class with proper this bindings. Otherwise you have to manually bind it using a regular function like the other answer describes.