состояние реакции — это одно состояние за нажатиями кнопок

#reactjs

#reactjs

Вопрос:

Я пишу простую страницу реакции, которая отображает 2 разные HTML-таблицы на основе того, какая кнопка нажата на экране. Проблема, с которой я сталкиваюсь, заключается в том, что таблица, которая отображается при каждом нажатии кнопки, связана с предыдущим нажатием кнопки. (Например, если я нажму кнопку 1 один раз, а затем нажму кнопку 2, отобразится таблица, связанная с кнопкой 1.)

Я новичок в react, поэтому, чтобы обновить таблицы, я переработал свой код, чтобы сохранить как можно больше состояния в App.js класс, я создал обратный вызов toggleState, чтобы связать нажатия кнопок с изменением состояния родительского элемента, а затем я передаю это в dataProvider через свойство endpoint. Я понимаю, что, вероятно, именно здесь происходит отключение состояния / пользовательского интерфейса, но я не уверен в причине, поскольку я придерживаюсь принципов react в меру своих возможностей.

моя структура класса выглядит следующим образом:

         App
    /        
   /          
  /            
DataProvider   ButtonToggle
|
Table
  

Если это важно, класс table создает таблицу на основе вызова API, я добавлю код для этого, но это не вызывает у меня проблем, поэтому я не считаю, что это источник проблемы.

App.js

 import React, { Component } from "react";
import PropTypes from "prop-types";
import DataProvider from "./DataProvider";
import Table from "./Table";
import ButtonToggle from "./ButtonToggle";

class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      input : 'employees',
      endpoint : "api/employees/"
    };
    console.log("constructor app: "   this.state.input   "n"   this.state.endpoint);
  }


  toggleState(input) {
    if(input == "employees") {
      this.setState({input : input, endpoint: "api/employees/"});
    }
    else {
      this.setState({input : input, endpoint: "api/categories/"});
    }
    console.log("toggleState "   this.state.input   "n"   this.state.endpoint);
  }

  render() {

    return (
      <div className="col-lg-12 grid-margin">
        <div className="card">
          <div className="card-title">
            <div className="row align-items-center justify-content-center">
              <div className="col-3"></div>
              <div className="col-6">
                <h1> Striped Table</h1>
              </div>
              <div className="col-3"></div>
            </div>
              <ButtonToggle toggleInput={ (input) => this.toggleState(input)}/>
            </div>
            <div className="card">
              <div className="card-title"></div>
              <div className="card-body">
              <DataProvider endpoint={this.state.endpoint}
                        render={data => <Table data={data} />} />
               </div>
            </div>
        </div>
      </div>
    );
  }
}


export default App;

  

DataProvider.js

 class DataProvider extends Component {

  static propTypes = {
    endpoint: PropTypes.string.isRequired,
    render: PropTypes.func.isRequired
  };
  constructor(props) {
    super(props);

    this.state = {
        data: [],
        loaded: false,
        placeholder: "Loading..."
    };
  }

  componentWillReceiveProps(props) {
    console.log("dataprov: "   this.props.endpoint);
    this.componentDidMount();
  }

  componentDidMount() {
     fetch(this.props.endpoint)
      .then(response => {
        if (response.status !== 200) {
        return this.setState({ placeholder: "Something went wrong" });
        }
      return response.json();
      })
       .then(data => this.setState({ data: data, loaded: true }));
  }

  render() {
    const { data, loaded, placeholder } = this.state;
    return loaded ? this.props.render(data) : <p>{placeholder}</p>;
  }
}
export default DataProvider;

  

ButtonToggle.js

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

  render() {
    return (
      <div className="row align-items-center justify-content-center">
      <div className="col-3 center-in-div">
        <button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'categories')}> Categories </button>
      </div>

        <div className="col-3 center-in-div">
          <button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'employees')}>
           Employees
         </button>
         </div>

          <div className="col-6"></div>
      </div>
    );
  }

}
export default ButtonToggle;

  

Table.js : Я не думаю, что это проблема, но я могу быть исправлен.

 import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";

const Table = ({ data }) =>
    !data.length ? (
    <p>Nothing to show. Records: {data.length} </p>
            ) : (
    <div className="table-responsive">
      <h2 className="subtitle">
    Showing <strong>{data.length} items</strong>
      </h2>
      <table className="table table-hover">
        <thead>
          <tr>
    {Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
          </tr>
        </thead>
        <tbody>
    {data.map(el => (
             <tr key={el.id}>
                 {Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
            </tr>
             ))}
        </tbody>
      </table>
    </div>
             );
Table.propTypes = {
    data: PropTypes.array.isRequired
};
export default Table;

  

Комментарии:

1. Первым шагом было бы прекратить привязку функции к ButtonToggle — поскольку вы обращаетесь this внутрь функции, вы хотите, чтобы она была привязана к родительской. Вы можете легко привязать его к родительскому, объявив его как функцию со стрелкой, toggleState = input => { , затем вызвать его изнутри ButtonToggle с помощью onClick={() => this.props.inputToggle("categories")} etc

Ответ №1:

Ниже приведен минимальный рабочий код, который я смог придумать. Ваши компоненты Button и Table могут быть немыми компонентами, которые будут получать данные из родительского компонента и представлять их. Ваш родительский компонент или контейнер будет иметь логику для установки свойств для кнопки и компонента таблицы. Поскольку компоненты таблицы и кнопки являются немыми, вы можете использовать функциональные компоненты.

Я добавил код для вызова api (я попытался имитировать вызов api) и получения данных в том же родительском компоненте, вы можете разделить их. Вы можете работать над стилем и проверками в соответствии с вашими потребностями. Дайте мне знать, если вам понадобится дополнительная помощь.

 class ParentComponent extends Component {
  constructor() {
    super();
    this.state = {
      name: "Category"
    }
    this.onBtnClick = this.onBtnClick.bind(this);
  }

  componentDidMount() {
    this.getData(this.state.name)
  }
  getData(name) {
    if (name === "Category") {
      this.apiCall("/Category").then((data) => {
        this.setState({ data: data })
      })
    } else {
      this.apiCall("/Employee").then((data) => {
        this.setState({ data: data })
      })
    }
  }

  apiCall(url) {
    return new Promise((res, rej) => {
      setTimeout(() => {
        if (url === "/Employee") {
          res([{ "Emp Name": "AAA", "Emp Age": "20" }, { "Emp Name": "BBB", "Emp Age": "40" }])
        } else {
          res([{ "Cat Id": "XXX", "Cat Name": "YYY" }, { "Cat Id": "MMM", "Cat Name": "NNN" }])
        }
      }, 1000)
    });

  }

  onBtnClick(name) {
    let newName = "Category"
    if (name === newName) {
      newName = "Employee"
    }

    this.setState({ name: newName, data: [] }, () => {
      this.getData(newName);
    })
  }
  render() {
    return (<>
      <ButtonComponent name={this.state.name} onBtnClick={this.onBtnClick}></ButtonComponent>
      <TableComponent data={this.state.data} />
    </>)
  }
}
const ButtonComponent = ({ name, onBtnClick }) => {
  return <Button onClick={() => { onBtnClick(name) }}>{name}</Button>
}


const TableComponent = ({ data }) => {
  function getTable(data) {
    return < table >
      <thead>
        <tr>
          {getHeading(data)}
        </tr>
      </thead>
      <tbody>
        {getRows(data)}
      </tbody>
    </table >
  }
  function getHeading(data) {
    return Object.entries(data[0]).map((key) => {
      return <th key={key}>{key[0]}</th>
    });
  }
  function getRows(data) {
    return data.map((row, index) => {
      return <tr key={"tr"   index}>
        {Object.entries(data[0]).map((key, index) => {
          console.log(row[key[0]]);
          return <td key={"td"   index}>{row[key[0]]}</td>
        })}
      </tr>
    })
  }
  return (
    data amp;amp; data.length > 0 ?
      getTable(data)
      : <div>Loading....</div>
  )
}