1
votes

I have a funky set up. I need a multi stage registration form. I have a parent:

class ContactPage extends React.Component {
constructor(props){
    super(props);
    this.state = {
        stage:0,
        name:'',
        message:'',
        email:'',
        phone:''
    }
    this.setName=(e)=>{
        this.setState({name:e});
    }
    this.setMessage=(e)=>{
        this.setState({message:e});
    }
    this.setEmail=(e)=>{
        this.setState({email:e});
    }
    this.setPhone=(e)=>{
        this.setState({phone:e});
    }

    this.nextStage=()=>{
        if(this.state.stage < 3){
            this.setState({stage:this.state.stage+1})
        }
    }
    this.previousStage=()=>{
        if(this.state.stage >= 1){
            this.setState({stage:this.state.stage-1})
        }
    }
    this.stage = [
        <ContactName onChange={this.setName} />,
        <ContactInfo />,
        <ContactMessage name={this.state.name} onChange={this.setMessage} />,
        <Send />
    ]
}

render(){
    return (
        <div>
           {this.stage[this.state.stage]}
           <button primary style={style.button} onClick={this.previousStage}> Previous </button>
                    <button primary style={style.button} onClick={this.nextStage}> Next </button>
                </div>

This component renders children based on in what stage of registration the user is. I can receive callbacks from children in parent(children do set the state of the parent), but, when passing state.name from parent to child as a prop, the child receives the initial state, which means the name is empty string.

Child component:

class ContactMessage extends React.Component {
constructor(props){
    super(props);
    this.state ={
        message:'',
        name:''
    }
    this.handleChange=(event)=>{
        this.props.onChange(event.target.value);
        this.setState({message: event.target.value});
    }
}

componentWillReceiveProps(props){
    this.setState({name:props.name})
}


render(){

    return(
    <div>
                <h1>{this.state.name}</h1>
                <form onSubmit={this.handleSubmit}>
                    <label htmlFor='messageField'>
                    Message:
                    <input className='messageField' type="textfield" value={this.state.message}
                    onChange={this.handleChange} />
                    </label>
              </form>
            </div>

UPDATE: I am receiving initial props in the child components, not updated props from parent state. How do I receive new and updated props from parent state?

3
Just a guess but I think that, since you're setting the stage props in the constructor, the value is set to the empty string, rather than a reference to the given state object.. - ViggoV
I was thinking the same way. But here is the deal. Im in first stage, i enter the name. The parent's state.name update, i go through to whatever stage, ant want to grab the state.name from the parent, via props, and I get empty string. How come I get the constructor state, although, the state was already updated? - Karolis Žukas
Because you set up all stages in the constructor. You need to set the stage props on render. You basically prerender the component with a value of "" and then add it afterwards.. I would probably write a method that returns the given stage from an index. - ViggoV
Can confirm, that when sending state as prop, I send out the old state. Lets say, if I pass state.stage to stage 3, the prop show stage:0. The question is. How do I send new and updated state, and not the initial value?? - Karolis Žukas
Wait a minute. I see you don’t bind “this” in your callbacks.. That means you’re probably setting the child components state instead! Try changing your callbacks to onChange={this.setMessage.bind(this)} so that when the child calls it “this” refers to the parent.. - ViggoV

3 Answers

0
votes

As I see, in ContactPage you trying to set event (e) as value of state.name instead of setting this.setState({ name: e.target.value }) in this. setName method. By the way, you don't have to pass everything through constructor. For example,

class MyComponent extends Component {
  state = {
    name: ''
  }

  handleChange = (e) => {
    this.setState({ [e.target.name]: e.target.value });
  }
  
  render() {
    return (
      <input name="name" value={this.state.name} onChange={this.handleChange} />
    );
  }
}
0
votes

Try extracting the stage array into its own method. Something like:

class ContactPage extends Component {
  constructor(props) {
    this.state = {
      stage: 0,
      name: 0
      // and so on
    }
  }
  getStage(index) {
    let stages = [
      <ContactName onChange={this.setName} />,
      <ContactInfo />,
      <ContactMessage name={this.state.name} onChange={this.setMessage} />,
      <Send />
    ];
    return stages[index];
  }
  render() {
    return (
      {this.getStage(this.state.stage)}
      <AllTheOtherStuff />
    )
  }
}
0
votes

3 things I'm noticing here, which may or may not be your problem.

First: Using state the way you are is inherently bug prone. If you are displaying this.state.name, and are setting name from this.props, both in the constructor and componentWillReceiveProps, then just skip the middle man and display this.props.name instead. Trying to maintain a correct state when the value in state is coming from props is really easy to mess up, thus it is better to just use props directly.

Second: I believe your problem is a mixture of 1 and 2 here, but you are declaring your stage variable in your constructor, so it is only going to use what it has available in the constructor. Move this.stage into your render() and it should display the correct state information.

Third: the way you are using componentWillReceiveProps can lead to confusion. It is better to name your variables something that accurately describes what they are, without possible confusion. I would change componentWillReceiveProps(props) to componentWillReceiveProps(nextProps) since nextProps is more explicit about what those props are you are dealing with.