DEV Community

Gyandeep Singh
Gyandeep Singh

Posted on • Originally published at gyandeeps.com on

React - How to use keys to avoid using getDerivedStateFromProps

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); 
Enter fullscreen mode Exit fullscreen mode

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 of NameForm component based on prop 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); 
Enter fullscreen mode Exit fullscreen mode

Observations

  • We added key to the NameForm 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.
  • 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 use key.
  • 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.

Top comments (1)

Collapse
 
bthntprlk profile image
batu

Nice information!

I only use key for mapping, I’ve never thought it’s about instance. Now, enlightened