简体   繁体   中英

React Filter the state without mutating the array

I know I am making a stupid mistake, but I can't seem to figure out what is it. I have a list in my state and depending on what the user selects from the dropdown, the state updates. But I am somehow mutating the state, so when the user selects something the second time, the list comes out to be empty and nothing displays on the screen.

This seems to be a popular question, and I have checked here , here and here .

import React, { Component } from 'react';
import axios from 'axios';
import Sidebar from './components/Sidebar'
import Map from './components/Map'

require('dotenv').config();

class App extends Component {
  state = {
    incidents: [],
    map: false,
    options: []
  }

  async componentDidMount() {
    const res = await axios.get('https://data.sfgov.org/resource/wr8u-xric.json', {
      params: {
        "$limit": 500,
        "$$app_token": process.env.APP_TOKEN
      }
    })

    const incidents = res.data;
    this.setState({ incidents });

    console.log(incidents)

    this.getOptions()
  };

  getMap = () => {
    this.setState({ map: true });
    console.log(this.state.options)
  }

  handleChange = (e) => {
    const items =  this.state.incidents.filter(incident => incident['zip_code'] === e.target.value)
    this.setState({incidents: items})
  }

  getOptions = () => {
    this.state.incidents.map(incident => {
      if(!this.state.options.includes(incident['zip_code'])){
        this.state.options.push(incident['zip_code'])
      }
    })
  }

  render() {
    return (
      <div>
        <h1> San Francisco Fire Incidents</h1>
        <button onClick={this.getMap}> Get Map</button>

        <div id="main">
          <div style={{ width: '20%', height: '900px', display: 'inline-block', overflow: 'scroll', marginRight: '2px' }}>
            <span> Zip Code</span>
            <form>
              <select value={this.state.value} onChange={this.handleChange}>
              {this.state.options.map(option => (
                <option value={option} key={option}>{option}</option>
              ))}

              </select>
            </form>
          </div>
          {
            this.state.map ? <Map incidents={this.state.incidents} /> : ''
          }
        </div>
      </div>
    );
  }
}

export default App;

The issue is that you don't maintain the initial state anywhere.

So after mutating the state and removing items, it's expected that the state variable will not contain all the original items.

Change to something like the following:

import React, { Component } from 'react';
import axios from 'axios';
import Sidebar from './components/Sidebar'
import Map from './components/Map'

require('dotenv').config();

class App extends Component {
  state = {
    initialIncidents: [], 
    incidents: [],
    map: false,
    options: []
  }

  async componentDidMount() {
    const res = await axios.get('https://data.sfgov.org/resource/wr8u-xric.json', {
      params: {
        "$limit": 500,
        "$$app_token": process.env.APP_TOKEN
      }
    })

    const incidents = res.data;
    this.setState({ initialIncidents: incidents });

    console.log(incidents)

    this.getOptions()
  };

  getMap = () => {
    this.setState({ map: true });
    console.log(this.state.options)
  }

  handleChange = (e) => {
    const items =  this.state.initialIncidents.filter(incident => incident['zip_code'] === e.target.value)
    this.setState({incidents: items})
  }

  getOptions = () => {
    this.state.incidents.map(incident => {
      if(!this.state.options.includes(incident['zip_code'])){
        this.state.options.push(incident['zip_code'])
      }
    })
  }

  render() {
    return (
      <div>
        <h1> San Francisco Fire Incidents</h1>
        <button onClick={this.getMap}> Get Map</button>

        <div id="main">
          <div style={{ width: '20%', height: '900px', display: 'inline-block', overflow: 'scroll', marginRight: '2px' }}>
            <span> Zip Code</span>
            <form>
              <select value={this.state.value} onChange={this.handleChange}>
              {this.state.options.map(option => (
                <option value={option} key={option}>{option}</option>
              ))}

              </select>
            </form>
          </div>
          {
            this.state.map ? <Map incidents={this.state.incidents} /> : ''
          }
        </div>
      </div>
    );
  }
}

export default App;

I think there is something wrong in this part

getOptions = () => {
this.state.incidents.map(incident => {
  if(!this.state.options.includes(incident['zip_code'])){
    this.state.options.push(incident['zip_code'])
  }
})

}

You are always mutating the state options when you search. When no results found, the options array would be empty.

Try to do something like this.

  getOptions = () => {
    let options = [...this.state.options]
    this.state.incidents.map(incident => {
      if(!this.state.options.includes(incident['zip_code'])){
        options.push(incident['zip_code'])
      }
    })
    this.setState({options});
  }

this.state.incidents contains all the incidents and when the user makes the first selection you are changing the incidents array . The second selection is filtering the already filtered incidents from the first selection and returning empty . This might be causing this issue .

  const items =  this.state.originalIncidents.filter(incident => incident['zip_code'] === e.target.value)
    this.setState({incidents: items})

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