简体   繁体   English

React 持有不超过一个数组元素的 state

[英]React holds state of no more than one array element

I've come to a halt making this covid19 app where I can see a list of countries on the left side of the screen with the option of adding any number of countries to the right side of the screen, which displays more covid data of the added country.我已经停止制作这个 covid19 应用程序了添加的国家。 I'm also kinda new to React.我对 React 也有点陌生。

Problem is, when I click the add button the added state is updated, and it displays that added country on the right side of the screen.问题是,当我单击添加按钮时,添加的 state 会更新,并在屏幕右侧显示添加的国家/地区。 But, when I try adding another country I get an error.但是,当我尝试添加另一个国家/地区时,出现错误。 I believe the error is somewhere around when I try to setState({ state }) in the addCountry method from within App.js.我相信当我尝试从 App.js 中的 addCountry 方法中 setState({ state }) 时,错误就在某个地方。

In other words, the 'added' state is only letting itself hold no more than one array element.换句话说,“添加”的 state 只会让自己容纳不超过一个数组元素。 Help much much much appreciated.非常感谢帮助。 I posted all the code.我发布了所有代码。

index.js index.js

import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

App.js应用程序.js

import CountryList from "./components/CountryList.js";
import Find from "./components/Find.js";
import Added from "./components/Added.js";

class App extends Component {
  constructor() {
    super();
    this.state = {
      countries: [],
      inputbox: [],
      added: [],
    };
  }

  // Arrow functions capture "this" when they are defined, while standard functions do when they are executed.
  // Thus, no need for the bind method. Awesome.
  handleChange = (e) =>
    this.setState({
      inputbox: e.target.value,
    });

  getCountryData = async (slug) => {
    const resp = await fetch(`https://api.covid19api.com/live/country/${slug}`);
    var addedData = await resp.json();
    // Api returns most days of covid, per country, that it tracks
    // Thus, we want the last tracked day of a country
    addedData = addedData[addedData.length - 1];
    return addedData;
  };

  // Add a country to the added state
  // Call when user clicks button associated with their desired country
  addCountry = async (btnId) => {
    const { countries, added } = this.state;
    var addedData = await this.getCountryData(btnId);
    countries.map((country) => {
      // If the button ID is equal to the current country in the loops' Slug
      if (btnId == country.Slug) {
        try {
          added.push([
            {
              addedCountry: addedData.Country,
              confirmedTotal: addedData.Confirmed,
              deathsTotal: addedData.Deaths,
              recoveredTotal: addedData.Recovered,
              activeTotal: addedData.Active,
            },
          ]);

          // (bug) IT IS PUSHING, BUT ITS NOT SETTING THE STATE!
          // ITS ONLY LETTING ME KEEP ONE ITEM IN THE STATE
          this.setState({ added });
          console.log(added);
        } catch (error) {
          alert(`Sorry, country data not available for ${country.Country}`);
          return;
        }
      }
    });
  };

  removeCountry = (btnId) => {
    const { added } = this.state;
    added.map((added, index) => {
      //console.log(added[index].addedCountry);
      if (btnId == added[index].addedCountry) {
        added.splice(index, 1);
        this.setState({ added: added });
      } else {
        console.log("not removed");
        return;
      }
    });
  };

  // Mount-on lifecycle method
  async componentDidMount() {
    const resp = await fetch("https://api.covid19api.com/countries");
    const countries = await resp.json(); // parsed response
    this.setState({ countries }); // set state to parsed response
  }

  render() {
    // Filter out countries depending on what state the inputbox is in
    const { countries, inputbox } = this.state;
    const filtered = countries.filter((country) =>
      country.Country.includes(inputbox)
    );

    return (
      <div className="App Container">
        <Find
          placeholder="Type to find a country of interest..."
          handleChange={this.handleChange}
        />
        <div className="row">
          <CountryList countries={filtered} addCountry={this.addCountry} />
          <Added added={this.state.added} removeCountry={this.removeCountry} />
        </div>
      </div>
    );
  }
}

export default App;
Added.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import AddedCountry from "./AddedCountry.js";

class Added extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="col-md-6">
        <Table>
          <thead>
            <tr>
              <th scope="col">#</th>
              <th scope="col">Country</th>
              <th scope="col">Active</th>
              <th scope="col">Confirmed Total</th>
              <th scope="col">Recovered</th>
              <th scope="col">Deaths</th>
              <th scope="col">Action</th>
            </tr>
          </thead>

          {this.props.added.map((added, index) => (
            <AddedCountry
              added={added[index]}
              removeCountry={this.props.removeCountry}
            />
          ))}
        </Table>
      </div>
    );
  }
}

export default Added;
AddedCountry.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";

class AddedCountry extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <tbody>
        <tr>
          <td></td>
          <td>{this.props.added.addedCountry}</td>
          <td>{this.props.added.activeTotal}</td>
          <td>{this.props.added.confirmedTotal}</td>
          <td>{this.props.added.recoveredTotal}</td>
          <td>{this.props.added.deathsTotal}</td>
          <td>
            {
              <Button
                onClick={() =>
                  this.props.removeCountry(
                    document.getElementById(this.props.added.addedCountry).id
                  )
                }
                id={this.props.added.addedCountry}
                type="submit"
                color="danger"
                size="sm"
              >
                Remove
              </Button>
            }
          </td>
        </tr>
      </tbody>
    );
  }
}

export default AddedCountry;
CountryList.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import Country from "./Country.js";

class CountryList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="col-md-6">
        <Table>
          <thead>
            <tr>
              <th scope="col">#</th>
              <th scope="col">Country</th>
              <th scope="col">Actions</th>
            </tr>
          </thead>

          {
            // Each country is a component
            // Function will display all countries as the Map function loops through them
            this.props.countries.map((country) => (
              <Country countries={country} addCountry={this.props.addCountry} />
            ))
          }
        </Table>
      </div>
    );
  }
}

export default CountryList;
Country.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";

class Country extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <tbody>
        <tr>
          <td></td>
          <td>{this.props.countries.Country}</td>
          <td>
            {
              <Button
                onClick={() =>
                  this.props.addCountry(
                    document.getElementById(this.props.countries.Slug).id
                  )
                }
                id={this.props.countries.Slug}
                type="submit"
                color="success"
                size="sm"
              >
                Add
              </Button>
            }
          </td>
        </tr>
      </tbody>
    );
  }
}

export default Country;
Find.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";

class Find extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="Find container">
        <br />
        <Form>
          <div className="form-row">
            <div className="form-group col-md-6">
              <h3>Find a Country</h3>
              <Input
                type="text"
                className="form-control"
                id="country"
                placeholder={this.props.placeholder}
                onChange={this.props.handleChange}
              ></Input>
            </div>
          </div>
        </Form>
      </div>
    );
  }
}

export default Find;

I haven't pored over all that code, but focusing right where you think the issue is it is obvious you are mutating your state object by pushing directly into the added array.我没有仔细研究所有这些代码,但是将重点放在您认为问题所在的地方,很明显您正在通过直接推入added的数组来改变您的 state object 。

Solution解决方案

Don't mutate state!不要改变状态!

Since it seems you only want to add a single new "add" and only when the button's btnId matches a country's slug, and the btnId can only ever be a valid value from the mapped countries array, I think this can be greatly simplified.由于您似乎只想添加一个新的“添加”并且仅当按钮的btnId与国家/地区的 slug 匹配时,并且btnId只能是映射countries地区数组中的有效值,我认为这可以大大简化。

addCountry = async (btnId) => {
  const addedData = await this.getCountryData(btnId);

  if (addedData) {
    this.setState(prevState => ({
      added: prevState.added.concat({ // <-- concat creates a new array reference
        addedCountry: addedData.Country,
        confirmedTotal: addedData.Confirmed,
        deathsTotal: addedData.Deaths,
        recoveredTotal: addedData.Recovered,
        activeTotal: addedData.Active,
      }),
    }));
  } else {
    alert(`Sorry, country data not available for ${country.Country}`);
  }
};

Similarly the removeCountry handler is mis-using the array mapping function and mutating the added state.同样, removeCountry处理程序误用了数组映射 function 并改变了added的 state。 Array.prototype.filter is the idiomatic way to remove an element from an array and return the new array reference. Array.prototype.filter是从数组中删除元素并返回新数组引用的惯用方式。

removeCountry = (btnId) => {
  this.setState(prevState => ({
    added: prevState.added.filter(el => el.addedCountry !== btnId),
  }));
};

Additional Issues & Suggestions其他问题和建议

Added.js添加了.js

If you maintain the added array as a flat array (not an array of arrays) then it's trivial to map the values.如果您将added的数组维护为平面数组(而不是数组数组),那么对于 map 的值来说是微不足道的。

{this.props.added.map((added) => (
  <AddedCountry
    key={added}
    added={added}
    removeCountry={this.props.removeCountry}
  />
))}

Country.js & AddedCountry.js Country.js 和添加的Country.js

I don't see any reason to query the DOM for the button id when you are literally right there and can enclose the country slug in the onClick callback.当您确实就在那儿并且可以在onClick回调中包含 country slug 时,我看不出有任何理由查询 DOM 以获取按钮 ID。

<Button
  onClick={() => this.props.addCountry(this.props.countries.Slug)}
  id={this.props.countries.Slug}
  type="submit"
  color="success"
  size="sm"
>
  Add
</Button>

<Button
  onClick={() => this.props.removeCountry(this.props.added.addedCountry)}
  id={this.props.added.addedCountry}
  type="submit"
  color="danger"
  size="sm"
>
  Remove
</Button>

App.js应用程序.js

This may or may not matter, but it is often the case to do case-insensitive search/filtering of data.这可能或可能无关紧要,但通常是对数据进行不区分大小写的搜索/过滤。 This is to ensure something like "France" still matching a user's search input of "france".这是为了确保像“France”这样的内容仍然匹配用户的“france”搜索输入。

const filtered = countries.filter((country) =>
  country.Country.toLowerCase().includes(inputbox.toLowerCase())
);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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