简体   繁体   中英

React Stepper use fetched list in state to next step as dropdown options - Cannot read property 'map' of undefined

I'm new to React and was creating a Stepper (wizard) form as seen in this digital Ocean link : https://www.digitalocean.com/community/tutorials/how-to-create-multistep-forms-with-react-and-semantic-ui

I changed that example ...instead the firsts 2 steps are only one dropdown choice each. In the first step the Dropdown is generated from my first axios fetch, and I want the second dropdown in the second step to show options based on the first selection. (Here suppose step 1 options is only a list of value 1, 2 or 3 for simplicity)

My problem is that I can't transfer my filtered list (objectsListOptionsForStep2) into the next step as I have this error : TypeError: Cannot read property 'map' of undefined ...My variable (objectsListOptionsForStep2) seems to be undefined when I go from step 1 to step 2 so I can't display the list as options in the dropdown.

In the MainForm I'm first calling axios and putting data in state. Then When I select the first option in the first dropdown list of the first step and when done I call my handleChange in which I grab the value selected and I conditionnaly call axios (if the step is =1) with the value selected as option.

When in the second step I can't use my filtered list as options ... I have this error described earlier.

Could somebody explain or show me how to do bring my list in state from step to step ? Thanks a lot !!!

Here is the MainForm.js

export class WizardMainForm extends Component {

  state = {
    error: null,
    step: 1,
    choiceOne: "",
    choiceTwo: "",
    objectsListOptionsForStep2: []
  };

  componentDidMount() {
    this.handleUserDetails();
  }

  handleUserDetails = () => {
     some code here
  };

  
  // Proceed to the next step
  nextStep = () => {
    const { step } = this.state;
    this.setState({
      step: step + 1,
    });
  };

  // Go back to previous step
  prevStep = () => {
    const { step } = this.state;
    this.setState({
      step: step - 1,
    });
  };

  //Handle fields change
  handleChange = (input) => (e) => {
    this.setState({ [input]: e.target.value });
    if (this.state.step == 1){
      const valueSelectedAtStepOne = e.target.value
      authAxios
      .get(`myUrl/?q=${valueSelectedAtStepOne}`)
      .then((res) => {
        this.setState({
          objectsListOptionsForStep2: res.data,
        });
        console.log(res.data);
      })
      .catch((err) => {
        this.setState({
          error: err.response.data.message,
        });
      });
    }
  };


  render() {
    const { step, choiceOne, objectsListOptionsForStep2 } = this.state;
    
    const values = {choiceOne, choiceTwo};
    
    switch (step) {
      case 1:
        return (
          <FormChoiceOne
            nextStep={this.nextStep}
            values={values}
            handleUserDetails={this.handleUserDetails}
            handleChange={this.handleChange}
          />
        );
      case 2:
        return (
          <FormChoiceTwo
            nextStep={this.nextStep}
            prevStep={this.prevStep}
            values={values}
            handleUserDetails={this.handleUserDetails} 
            handleChange={this.handleChange}
          />
        );
    }
  }
}

export default WizardMainForm;

Here if FormChoiceOne.js

    export class FormChoiceOne extends Component {
      
        continue = (e) => {
        e.preventDefault();
        this.props.nextStep();
      };
    
      render() {
       return (
        <div>
         <Form>
            <select
              onChange={handleChange("choiceOne")}
              defaultValue={values.choiceOne}
            >
              <option>1</option>
              <option>2</option>
              <option>3</option>
            </select>
            <Button onClick={this.continue}> Next step </Button>
         </Form>
        </div>
    );
  }
}

export default FormChoiceOne;

Here is FormChoiceTwo.js This is where I have my problems...

export class FormChoiceTwo extends Component {

continue = (e) => {
    e.preventDefault();
    this.props.nextStep();
  };

  back = (e) => {
    e.preventDefault();
    this.props.prevStep();
  };

render() {
    
some code

    return (
      <div>
       <Form>
        <select>
          {this.objectsListOptionsForStep2.map((u) => (
            <option>{u.id}</option>
          ))}
         </select>
            <Button onClick={this.back}> Previous step </Button>
            <Button onClick={this.continue}> Next step </Button> 
       </Form>
      </div>
    );
  }
}


export default FormChoiceTwo;

According to your comment try this :

{
(objectsListOptionsForStep2 === null || objectsListOptionsForStep2===undefined) ? 
    <option>Loading</option> : 
    this.objectsListOptionsForStep2.map((u) => (
            <option>{u.id}</option>
          ))
}

Tell me what's happening now. And try to create a sandbox at codesandbox.io

According to your code in sandbox, I'm editing here : https://codesandbox.io/s/silly-swirles-kv61w?file=/src/MainForm.js:852-878 . I can tell you that

  • The problem is in the API
  • https://swapi.dev/api/planets/{value}/ returning HTML, but it should return JSON, now we have to parse somehow.
  • You were never passing objectsListOptionsForStep2 to Form2
  • And you have to wait for trigger after fetching value to that object, before rendering
  • no need to use this, now I can understand your code

If you can change the API, let me know. And if I can figure the API part I will update the answer.

You can't access parent component's state (in this case WizardMainForm ) from child component (in this case FormChoiceTwo ) directly with this.objectsListOptionsForStep2 . You have to pass it to a child via props, so something like this should work:

  1. In you mainForm.js file pass data you need as prop value to the FormChoiceTwo component. For example:
<FormChoiceTwo
  nextStep={this.nextStep}
  prevStep={this.prevStep}
  values={values}
  handleUserDetails={this.handleUserDetails} 
  handleChange={this.handleChange}
  objectsListOptionsForStep2={objectsListOptionsForStep2}
/>
  1. Inside FormChoiceTwo access it via this.props . Example:
<select>
  {this.props.objectsListOptionsForStep2.map((u) => (
    <option>{u.id}</option>
  ))}
</select>

You can also use destructuring inside FormChoiceTwo like const { objectsListOptionsForStep2 } = this.props; and then you can remove this.props inside select and just write objectsListOptionsForStep2.map

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM