Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS - How to properly initialize state from props which is populated by fetching data?

Here is my editing component:

class EditField extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }
  edit(e) {
    this.setState({ value: e.target.value });
    if (e.keyCode === 13) {
      this.props.onEdited(this.state.value);
    }
  }
  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.value}
          onChange={this.edit.bind(this)}
        />
      </div>
    )
  }
}

I need to populate state from props like this:

function Container({ entity, onEdited }) {
  return (
    <div>
      <EditField onEdited={onEdited} value={entity.firstName} />
      <EditField onEdited={onEdited} value={entity.lastName} />
    </div>
  );
}

The Container component get onEdited and entity props from redux store. Container's parent will handle data fetching and onEdited (which will only be triggered if user hit Enter) will dispatch request to the server.

My problem is how to initialize value props properly? Because if I use:

  componentDidMount() {
    this.setState({
      value: this.props.value
    });
  }

I got empty state because fetching data is not finished when componentDidMount called. And if I use:

  componentWillReceiveProps(nextProps) {
    this.setState({
      value: nextProps.value
    });
  }

I got this warning:

Warning: EditField is changing a controlled input of type text to be
unncontrolled. Input elements should not switch from controlled to
uncontrolled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.

So, how to do this correctly?

like image 313
Mas Bagol Avatar asked Aug 07 '16 14:08

Mas Bagol


People also ask

Can we initialize state with props in React?

In some cases it is possible, but only if you are sure that you do not want the component to react to the changes in props, like in our example with the initial state - if it can never be changed, it is perfectly fine to initialize the state with it.

How do I initialize a state in React js?

Initialize State Without Constructor Another way of initializing state in React is to use the Class property. Once the class is instantiated in the memory all the properties of the class are created so that we can read these properties in the render function.

How do you get state from props?

Component { state = { description: '' } constructor (props) => { const { description } = props; this. state = {description}; } render () { const {state: { description }} = this; return ( <input type="text" value={description} /> ); } } export default SecondComponent; Update: I changed setState() to this.

What will happen if you use props in initial state in React?

Where the props are used to set the initial state of the Component is a general anti-pattern of React. This implies that the state of the component is tied to the props of the component. The issue with doing this is that the constructor is only ever called once in the life cycle of the component.


2 Answers

This is what I recommend:

  • You could use getInitialState from EditField to populate the value state from the value prop. But this won't work, because getInitialState will only be called once, so subsequent renders will not update the state. Besides, this is an anti-pattern.
  • You should make the EditField component controlled. Always pass the current value as prop and stop dealing with state at all. If you want a library to help you link the input state with Redux, please take a look at Redux-Form.
  • The onEdited event you created, at least the way you did it, doesn't play well with controlled inputs, so, what you want to do is to have an onChange event that is always fired with the new value, so the Redux state will always change. You may have another event triggered when the user hits enter (e.g onEnterPressed), so you can call the server and update the entity values. Again. Redux-Form can help here.
  • Apparently entity.firstName and entity.lastName can only contain the values that the user has confirmed (hit enter), not temporary values. If this is the case, try to separate the state of the form from the state of the entity. The state of the form can be controlled by Redux-Form. When the user hits enter, you can trigger an action that actually calls the server and updates the state of the entity. You can even have a "loading" state so your form is disabled while you're calling the server.
like image 193
André Pena Avatar answered Sep 20 '22 14:09

André Pena


Since Container subscribes to Redux store, I suggest make the EditField stateless functional component. Here's my approach:

const EditField = ({
  onEdited,
  value
}) => (
    <div>
      <input
        type="text"
        value={value}
        onChange={onEdited}
      />
    </div>
);

class Container extends React.Component {

  constructor(props) {
    super(props);
    this.state = {value: ''};
  }

  edit = (e) => {
    this.setState({value: e.target.value});
    e.keyCode === 13 ? this.props.onEdited(this.state.value) : null;
  };

  sendValue = (val) => val ? val : this.state.value;

  render() {
    this.props = {
      firstName: "Ilan",
      lastName: null
    }
    let { firstName, lastName, onEdited } = this.props;
    return (
      <div>
        <EditField onEdited={this.edit} value={this.sendValue(firstName)} />
        <EditField onEdited={this.edit} value={this.sendValue(lastName)} />
      </div>      
    )
  }
}

ReactDOM.render(<Container />, document.getElementById('app'));

A live demo: https://codepen.io/ilanus/pen/yJQNNk

Container will send either firstName, lastName or the default state...

like image 24
Ilanus Avatar answered Sep 21 '22 14:09

Ilanus