When to really use getDerivedStateFromProps?

330 Views Asked by At

I'm having trouble determining if my component hierarchy really needs getDerivedStateFromProps, if the cases where it is needed is really as rare as the documentation makes it sound. It might be a fundamental misunderstanding about React/Redux design.

class AttributeList extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      attributes: props.attributes,
      newAttributes: []
    }
  }

  addNewAttribute = () => {
      // add new empty attribute to newAttributes state
  }

  onKeyChange = () => {
      // update appropriate attribute key
  }

  onValueChange = () => {
      // update appropriate attribute value
  }

  saveAttributes = () => {
      // save the, API call
  }

  render = () => {
      this.state.attributes.map((pair) => {
          <Attribute
            //pass data + functions, functional component />
      })
      this.state.newAttributes.map((pair) => {
          <Attribute
            //pass data + functions, functional component />
      })
  }

  static getDerivedStateFromProps(){
      // ?? do comparisons here to choose to remove or keep certain newAttributes? or just ignore result of save and keep interface as-is, just show error message if saving failed. 
  } 
}

I have a parent component AttributeList which renders a bunch of Attributes, which are essentially key-value pairs. AttributeList receives the list of attributes of a document as props. However, the attributes can be edited, so it initializes its state (this.state.attributes) with this.props.attributes. Normally keys are immutable, but if a user adds a new attribute to the list, he can edit both the key and value. At any point, a user can save all the attributes. When the new attributes are saved, I'd like to disabled editing the keys for them as well. Here is the dilemma.

Option one is to save the document and just hope it worked, and then clear the new attributes list and mark all the attributes as saved (disabling the key input). I think this would be the "fully uncontrolled" solution, where once the state is initialized the component deals with everything on it's own. However, what if the save fails? I don't want to show and incorrect state to the user.

So I want to do option two. After save, fetch the document, which will load the attribute list and re-render the component. However I need to get rid of my new attributes since they are now a part of the attributes prop. I would like to verify that the new attributes are actually a part of the attributes prop now. It seems like this would happen ingetDerivedStateFromProps where I would on each render cycle check if any new attribute keys already exist in the attributes prop, and remove them from the "new" list if they do, and return that state.

But is this really the right time to use getDerivedStateFromProps? It seems to me that for any page that a user is "editing" something where you make an API call to save it, if you want to render based on the saved data ("the truth"), then I need to use getDerivedStateFromProps. Or perhaps from a design perspective it is better to show a message akin to "data not successfully saved" and keep the state as is, to prevent any data loss. I'm honestly not sure.

1

There are 1 best solutions below

4
On

I don't see how getDerivedStateFromProps comes into it as there's no reason you need to copy props into state is there? When an old attribute value is changed you save it to the redux store, when new attribute properties are changed you can update local state (or save them to a different slice of the store, or differentiate them some other way). Update rules can be enforced in the update handlers or during merge on save.

// dispatch redux action to update store
onOldValueChange = () => {}

// this.setState to update new value
onNewKeyChange = () => {}
onNewValueChange = () => {}

render = () => {
  this.props.attributes.map((pair) => {
    <Attribute
      //pass data + onOldValueChange, functional component />
  })
  this.state.newAttributes.map((pair) => {
    <NewAttribute
      //pass data + functions, functional component />
  })
}