Background
Until today I used key
in react
only when react dev threw warnings inside the console. Other than that I never cared about keys and never invested time to understand them. This is me being brutally honest to you all readers.
Problem
Have a component which display person name form based on different name and id passed to it. So using that form user can edit person details.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class NameForm extends Component {
constructor(props) {
super(props);
this.state = {
userId: props.userId || 0,
name: props.name || ""
};
}
handleChange = event => {
this.setState({ name: event.target.value });
};
handleSubmit = event => {
console.log("A name was submitted: " + this.state.name);
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: {
1: "A",
2: "B",
3: "C"
},
editUserId: "new"
};
}
onChange = event => {
const value = event.target.value;
this.setState(() => ({
editUserId: value
}));
};
render() {
const { editUserId, users } = this.state;
return (
<div>
<span>Select Edit userId: </span>
<select onChange={this.onChange}>
<option value="new">New User</option>
{Object.entries(users).map(([userId, name]) => (
<option value={userId}>{name}</option>
))}
</select>
<NameForm userId={editUserId} name={users[editUserId] || ""} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Observations
- Form name field displays no data since by default its set to new user.
- Now when you select user
A
from the select list, it still shows name as empty. - Since react thinks its the same component so it will not call the
constructor
again. - At this point you will be thinking that I need to change
state
ofNameForm
component based onprop
changes.- This is called syncing state with props.
- And now you will be convinced that you need to use
getDerivedStateFromProps
static method because you need to change state based on prop change. - This is where you went wrong.
Solution
Let me quote a line from React documentation:
If you want to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.
Make NameForm
component controlled by using key
for React elements.
That's it.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class NameForm extends Component {
constructor(props) {
super(props);
this.state = {
userId: props.userId || 0,
name: props.name || ""
};
}
handleChange = event => {
this.setState({ name: event.target.value });
};
handleSubmit = event => {
console.log("A name was submitted: " + this.state.name);
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: {
1: "A",
2: "B",
3: "C"
},
editUserId: "new"
};
}
onChange = event => {
const value = event.target.value;
this.setState(() => ({
editUserId: value
}));
};
render() {
const { editUserId, users } = this.state;
return (
<div>
<span>Select Edit userId: </span>
<select onChange={this.onChange}>
<option value="new">New User</option>
{Object.entries(users).map(([userId, name]) => (
<option value={userId}>{name}</option>
))}
</select>
<NameForm key={editUserId} userId={editUserId} name={users[editUserId] || ""} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Observations
- We added
key
to theNameForm
component.- This tells react to create a new instance of
NameForm
based on the key. - With that, component
constructor
will get called and the state is maintained.
- This tells react to create a new instance of
- Now when you change values from the dropdown the form values will change.
Conclusion
- When you think about using
getDerivedStateFromProps
, take a step back and think through the problem and see if you can usekey
. - That's why you will see people say that you should avoid or use
getDerivedStateFromProps
rarely. - This was an eye opener for me when I actually used it. While reading docs I didn’t get the whole point.
Liquid error: internal
Top comments (1)
Nice information!
I only use key for mapping, I’ve never thought it’s about instance. Now, enlightened