React Anti-Patterns: Props in Initial State

Justin Tulk
Oct 7, 2016 · 3 min read

I wrote a post the other day explaining an approach I’m using to reset a form component to its initial state when a user presses a ‘cancel’ button. Someone called out my solution as an ‘anti-pattern’ in the comments though, because I’m initializing state in the component with props.

constructor(props){
super(props)
this.state = {
inputVal: props.inputValue
}
// preserve the initial state in a new object
this.baseState = this.state
}

Background

The React docs call this out as an anti-pattern:

Using props to generate state in getInitialState often leads to duplication of “source of truth”, i.e. where the real data is. This is because getInitialState is only invoked when the component is first created.

The danger is that if the props on the component are changed without the component being ‘refreshed’, the new prop value will never be displayed because the constructor function (or getInitialState) will never update the current state of the component. The initialization of state from props only runs when the component is first created.

Simple enough. However, reading further uncovers a pretty explicit exception to this rule:

However, it’s not an anti-pattern if you make it clear that the prop is only seed data for the component’s internally-controlled state:

That is, if your component doesn’t need to stay aware of potential changes in the prop value it’s fine to initialize state this way. Off the top of my head, I can’t think of a lot of easy examples where this would be true. The best I can think of would be:

class MyContainer extends Component {
toggleEdit = () => {
this.setState({ isEditing: !this.state.isEditing })
}

submitChange = () => {
// trigger this to submit the value and have it
// come back into the component as this.props.inputValue
this.setState({ isEditing: false })
}

render() {
return (
<div>
{
this.state.isEditing &&
<MyInput
cancelFunc={this.toggleEdit}
submitFunc={this.submitChange}
value={this.props.inputValue} />
}
{
!this.state.isEditing &&
<MyValueDisplay value={this.props.inputValue} />
}
<button onClick={this.toggleEdit} type='button' />
</div>
)
}
}

The way this would work would that the form returned from <MyInput /> would only show during the { isEditing: true } state. Any ‘submit’ or ‘cancel’ event would unmount the component and show the value currently in props via the <MyValueDisplay /> component.

I’m currently doing this in certain view that differentiate between an ‘edit’ and a ‘display’ mode, but it’s definitely not the most common.

Workarounds

I haven’t done this in practice, but a simple work-around could be:

// reset state if the seeded prop is updatedcomponentWillReceiveProps(nextProps){
if (nextProps.inputValue !== this.props.inputValue) {
this.setState({ inputVal: nextProps.inputValue })
}

}

Should I or Shouldn’t I?

If you’re thinking about whether or not to seed state data into a React component via props, the questions I think you need to ask yourself are:

  • What’s the lifecycle for this component? Will it hang around very long without being refreshed?
  • Can the values that come in through props be changed elsewhere? and if they are, should this component display the updated value?
  • If I mutate these seed values in state, will they be needed to re-seed this component on a ‘save’ event (or similar)?

— — — — — — — — — — — — — — — — —

Related: more thoughts on anti-patterns

Justin Tulk

Written by

Senior Software Engineer. Making computers do stuff since 2011.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade