Search code examples
javascriptmodelreduxnormalize

sync state with props to achive two way binding for form input in reactjs


i have a very long form (75 input to be exact), since i'm using redux to manage my application state, whenever i want to edit this form i want to setState of form to the prop to allow editing.

Example Code:

class VisitCard extends Component {
  constructor(props) {
    super(props); //props.visit = {name:'randome name', data:'etc..'}
    this.state = Object.assign({},props.visit);
    this.bindInput = this.bindInput.bind(this);
  }
  //BindInput will return props for Inputs, to achive two way binding
  bindInput(config){
    const {name,...props} = config;
    return {
      value   : this.state[name],
      onChange: event => this.setState({[name]:event.target.value}),
      ...props
    }
  }

  render(){
  return <div>
   <input {...this.bindInput({name:'name', type:'text'})} />
   <input {...this.bindInput({name:'data', type:'text'})} />
  </div>

  }
}

above code works perfect, problem is when this component mounts, it give me error "Cannot update during an existing state transition"

also sometimes if the value is not predefined in the props, the value for input will be undefined, so after props load from server and updates component i get another error "trying to change input from uncontrolled to controled" that is because this.state[name] was undefined then i got a value

so what am'i doing wrong ? how can i link the state of component with props value and make sure that if props changed, state does change too, while at sametime, if state changes this does not affect props.


Solution

  • I hope modify your code to match the below logic will resolve your issues. Look for comments inside the code for explanations

    class VisitCard extends Component {
      constructor(props) {
        super(props); 
        //set your state to have a key that holds your prop value.
        this.state = { visit: props.visit };
        this.bindInput = this.bindInput.bind(this);
      }
    
      componentWillReceiveProps(nextProps) {
        //if your props is received after the component is mounted, then this function will update the state accordingly.
        if(this.props.visit !== nextProps.visit) {
         this.setState({visit: nextProps.visit});
        }
      }
    
      bindInput(config){
        const {name,...props} = config;
        // return defaultValue which you get from the props.
        // you can add `value: this.state.visit[name]` to the below object only if you want your input to be controlled, else it can be ignored.
        return {
          defaultValue   : this.props.visit[name],
          onChange: event => this.setState(
                {visit: { ...this.state.visit,
                          [name]:event.target.value}
                }),
          ...props
        }
      }
    
      render(){
       // render empty if your props has not yet arrived.
      if(!this.props.visit) {
        return (<div />);
      }
    
      // render after you have values in props
      return (<div>
       <input {...this.bindInput({name:'name', type:'text'})} />
       <input {...this.bindInput({name:'data', type:'text'})} />
      </div>);
    
      }
    }