Initialize Empty Class Array and populate from fetch Fetch function React

Viewed 713

I am trying to populate a dropdown with values from API. I declared empty array in react class but cannot assign the values to it. I cannot use it as state variables as I have to make lot of changes to previously developed code. The way I did the code it says options is not defined. The partial code is posted below which is causing the problem. Any help is really appreciated.

export default class LoadLimits extends Component {
   constructor(props){
    super(props);
    this.options = []
    this.getZoneOptions = this.getZoneOptions.bind(this)
   }
   render(){
       return (
          <PtSelect label="Assigned Zone" options={options} 
                  onChange={this.onChangeDropdown} 
                    disabled={this.props.disabled}
                     defaultVal={this.state.assignedZone} name="assignedZone" />                        
     )
   }
   getZoneOptions =  () =>{
        const zoneOptions = []
        const keys = []
        fetch(`${config.server}/getzoneOptions/`+this.props.ownModel.agencyId)
        .then(response=>
            {
                return response.json();
            })
            
        .then(data=>{
            for (var i =0;i<data[0].length;i++){
                if (data[0][i]['Zone_key']!==998){
                zoneOptions.push(data[0][i]['description'])
                keys.push(data[0][i]['Zone_key'])
                }
            }
           let dropOptions = zoneOptions.map((option,idx)=>{
            return {key:keys[idx],value: option, label:option}
           });   
           this.options = dropOptions
       })
        
        .catch(error=>{
            console.log(error);
        });
    
    }

}
1 Answers

Issue

The options being passed to PtSelect is not defined.

<PtSelect
  label="Assigned Zone"
  options={options} // <-- should be this.options
  onChange={this.onChangeDropdown}
  disabled={this.props.disabled}
  defaultVal={this.state.assignedZone}
  name="assignedZone"
/>

Solution

If you need a variable to hold a value that you don't want coupled to the React component lifecycle then you should probably use a React ref.

  1. Import createRef from 'react'.
  2. Create a mutable ref for the options.
  3. Implement the componentDidMount lifecycle method to populate and set the current value of the options.
  4. Pass the current options value to the PtSelect component.

Code

import React, { Component, createRef } from 'react';

class LoadLimits extends Component {
  constructor(props) {
    super(props);
    this.getZoneOptions = this.getZoneOptions.bind(this);

    this.options = createRef([]);
  }

  getZoneOptions = () => {
    const zoneOptions = [];
    const keys = [];
    fetch(`${config.server}/getzoneOptions/` + this.props.ownModel.agencyId)
      .then((response) => {
        return response.json();
      })

      .then((data) => {
        for (var i = 0; i < data[0].length; i++) {
          if (data[0][i]["Zone_key"] !== 998) {
            zoneOptions.push(data[0][i]["description"]);
            keys.push(data[0][i]["Zone_key"]);
          }
        }
        const dropOptions = zoneOptions.map((option, idx) => {
          return { key: keys[idx], value: option, label: option };
        });
        this.options.current = dropOptions;
      })
      .catch((error) => {
        console.log(error);
      });
  };

  componentDidMount() {
    this.getZoneOptions();
  }

  render() {
    return (
      <PtSelect
        label="Assigned Zone"
        options={this.options.current}
        onChange={this.onChangeDropdown}
        disabled={this.props.disabled}
        defaultVal={this.state.assignedZone}
        name="assignedZone"
      />
    );
  }
}

Alternative Solution - Use forceUpdate (not strongly suggested)

In addition to addressing the this.options issue in PtSelect, you can use forceUpdate to tell React to rerender regardless of any state and/or prop update. This should rerender the select with populated options.

component.forceUpdate(callback)

By default, when your component’s state or props change, your component will re-render. If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate().

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().

getZoneOptions = () => {
  const zoneOptions = [];
  const keys = [];
  fetch(`https://jsonplaceholder.typicode.com/users`)
    .then((response) => {
      return response.json();
    })

    .then((data) => {
      for (var i = 0; i < data.length; i++) {
        zoneOptions.push(data[i]["name"]);
        keys.push(data[i]["id"]);
      }
      let dropOptions = zoneOptions.map((option, idx) => {
        return { key: keys[idx], value: option, label: option };
      });
      this.options = dropOptions;
      console.log("Options ", this.options);
      this.forceUpdate(); // <-- trigger a rerender
    })

    .catch((error) => {
      console.log(error);
    });
};
Related