Предотвратить сброс дочернего состояния после изменения состояния родительского компонента также получить значения всех дочерних компонентов: ReactJS + Typescript

Я немного новичок в реакции, и я застрял в этой ситуации, когда я реализую настраиваемый раскрывающийся фильтр для таблицы в реакции. У меня есть набор значений раскрывающегося списка для каждого столбца и есть кнопка «Применить».

Я сохранил для этого дочерний компонент, который принимает значения из раскрывающегося списка и отправляет выбранный обратно родительскому элементу. Затем я вызываю внутренний API, который предоставляет мне отфильтрованные данные, которые, в свою очередь, устанавливают состояние родителей. Проблема здесь в том, что значения флажков в раскрывающемся списке теряются после того, как я получаю данные и устанавливаю родительское состояние.

Каждый дочерний компонент имеет набор флажков, кнопку «Применить» и кнопку «Очистить». Поэтому, щелкнув «Применить», я должен отправить отмеченный элемент родительскому элементу или в целом, в зависимости от того, что отмечен, без потери предыдущего содержимого.

Я не могу понять, почему теряю значения флажков?

Было бы очень полезно, если бы кто-нибудь мог мне помочь с этим

Песочница: https://codesandbox.io/s/nervous-elgamal-0zztb

Я добавил ссылку на песочницу с соответствующими комментариями. Пожалуйста, посмотрите. Я немного новичок, чтобы отреагировать.

Помощь будет очень признательна

Родитель

import * as React from "react";
import { render } from "react-dom";
import ReactTable from "react-table";
import "./styles.css";
import "react-table/react-table.css";
import Child from "./Child";
interface IState {
  data: {}[];
  columns: {}[];
  selectedValues: {};
  optionsForColumns: {};
}

interface IProps {}

export default class App extends React.Component<IProps, IState> {

  // Here I have  hardcoded the values, but data and optionsForColumns comes from the backend and it is set inside componentDidMount
  constructor(props: any) {
    super(props);
    this.state = {
      data: [
        { firstName: "Jack", status: "Submitted", age: "14" },
        { firstName: "Simon", status: "Pending", age: "15" }
      ],
      selectedValues: {},
      columns: [],
      optionsForColumns: {
        firstName: [{ Jack: "4" }, { Simon: "5" }],
        status: [{ Submitted: "5" }, { Pending: "7" }]
      }
    };
  }

  // Get the values for checkboxes that will be sent to child
  getValuesFromKey = (key: any) => {
    let data: any = this.state.optionsForColumns[key];
    let result = data.map((value: any) => {
      let keys = Object.keys(value);
      return {
        field: keys[0],
        checked: false
      };
    });
    return result;
  };

  // Get the consolidated values from child and then pass it for server side filtering
  handleFilter = (fieldName: any, selectedValue: any, modifiedObj: any) => 
  {
    this.setState(
      {
        selectedValues: {
          ...this.state.selectedValues,
          [fieldName]: selectedValue
        }
      },
      () => this.handleColumnFilter(this.state.selectedValues)
    );
  };

  // Function that will make server call based on the checked values from child
  handleColumnFilter = (values: any) => {
    // server side code for filtering
    // After this checkbox content is lost
  };

  // Function where I configure the columns array for the table . (Also data and column fiter values will be set here, in this case I have hardcoded inside constructor)
  componentDidMount() {
    let columns = [
      {
        Header: () => (
          <div>
            <div>
              <Child
                key="firstName"
                name="firstName"
                options={this.getValuesFromKey("firstName")}
                handleFilter={this.handleFilter}
              />
            </div>
            <span>First Name</span>
          </div>
        ),
        accessor: "firstName"
      },
      {
        Header: () => (
          <div>
            <div>
              <Child
                key="status"
                name="status"
                options={this.getValuesFromKey("status")}
                handleFilter={this.handleFilter}
              />
            </div>
            <span>Status</span>
          </div>
        ),
        accessor: "status",
      },
      {
        Header: "Age",
        accessor: "age"
      }
    ];
    this.setState({ columns });
  }

  //Rendering the data table
  render() {
    const { data, columns } = this.state;
    return (
      <div>
        <ReactTable
          data={data}
          columns={columns}
        />
      </div>
    );
  }
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);

Ребенок


import * as React from "react";
import { Button, Checkbox, Icon } from "semantic-ui-react";
interface IProps {
  options: any;
  name: string;
  handleFilter(val1: any, val2: any, val3: void): void;
}
interface IState {
  showList: boolean;
  selected: [];
  checkboxOptions: any;
}
export default class Child extends React.Component<IProps, IState> {
  constructor(props: any) {
    super(props);
    this.state = {
      selected: [],
      showList: false,
      checkboxOptions: this.props.options.map((option: any) => option.checked)
    };
  }

  // Checkbox change handler
  handleValueChange = (event: React.FormEvent<HTMLInputElement>, data: any) => {
    const i = this.props.options.findIndex(
      (item: any) => item.field === data.name
    );
    const optionsArr = this.state.checkboxOptions.map(
      (prevState: any, si: any) => (si === i ? !prevState : prevState)
    );
    this.setState({ checkboxOptions: optionsArr });
  };

  //Passing the checked values back to parent
  passSelectionToParent = (event: any) => {
    event.preventDefault();
    const result = this.props.options.map((item: any, i: any) =>
      Object.assign({}, item, {
        checked: this.state.checkboxOptions[i]
      })
    );
    const selected = result
      .filter((res: any) => res.checked)
      .map((ele: any) => ele.field);
    console.log(selected);
    this.props.handleFilter(this.props.name, selected, result);
  };

  //Show/Hide filter
  toggleList = () => {
    this.setState(prevState => ({ showList: !prevState.showList }));
  };

  //Rendering the checkboxes based on the local state, but still it gets lost after filtering happens
  render() {
    let { showList } = this.state;
    let visibleFlag: string;
    if (showList === true) visibleFlag = "visible";
    else visibleFlag = "";
    return (
      <div>
        <div style={{ position: "absolute" }}>
          <div
            className={"ui scrolling dropdown column-settings " + visibleFlag}
          >
            <Icon className="filter" onClick={this.toggleList} />
            <div className={"menu transition " + visibleFlag}>
              <div className="menu-item-holder">
                {this.props.options.map((item: any, i: number) => (
                  <div className="menu-item" key={i}>
                    <Checkbox
                      name={item.field}
                      onChange={this.handleValueChange}
                      label={item.field}
                      checked={this.state.checkboxOptions[i]}
                    />
                  </div>
                ))}
              </div>
              <div className="menu-btn-holder">
                <Button size="small" onClick={this.passSelectionToParent}>
                  Apply
                </Button>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}





person VJR08    schedule 18.07.2019    source источник
comment
Добавить каждое полученное значение в родительский массив состояний?   -  person Chris G    schedule 18.07.2019
comment
В приведенной выше ссылке stackblitz содержимое флажка не потеряно, как я вижу.   -  person Munim Munna    schedule 25.07.2019
comment
@Munim Munna, да, я не добавил код на стороне сервера .. После того, как возвращаются отфильтрованные дни, значения флажков теряются   -  person VJR08    schedule 25.07.2019
comment
Здесь я установил data с тайм-аутом, но значения флажков сохраняются.   -  person Munim Munna    schedule 25.07.2019
comment
См. Ссылку, которую я разместил выше, изменение data в вашем handleColumnFilter методе не сбрасывает флажки.   -  person Munim Munna    schedule 26.07.2019


Ответы (2)


Похоже, это случай неудобного управления государством. В настоящее время управление состоянием осуществляется на дочернем уровне, но было бы проще управлять на родительском уровне. Это называется поднятием состояния в React.

Суть - общее состояние управляется в родительском компоненте и обновляется путем вызова функции, переданной дочернему компоненту. При нажатии кнопки «Применить» выбранное значение переключателя передается родительскому элементу, который объединяет новый выбор с общим состоянием.

Я создал минимальный пример вашего кода, показывающий, как мы можем поднять состояние от дочернего до родительского компонента. Я также использую несколько новых функций React, например useState, чтобы упростить дочерний компонент.

// Child Component
const Child = ({name, options, updateSelections}) => {
  const [selected, setSelected] = React.useState([]);
  const handleChange = (event) => {
    let updated;
    if (event.target.checked) {
      updated = [...selected, event.target.value];
    } else {
      updated = selected.filter(v => v !== event.target.value);
    }
    setSelected(updated);
  }
  
  const passSelectionToParent = (event) => {
    event.preventDefault();
    updateSelections(name, selected);
  }

  return (
    <form>
      {options.map(item => (
        <label for={name}>
          <input
            key={name}
            type="checkbox"
            name={item}
            value={item}
            onChange={handleChange}
          />
          {item}
        </label>
      ))}
      <button onClick={passSelectionToParent}>Apply</button>
    </form>
  )
}

// Parent Component
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.fields = ["firstName", "status"],
    this.state = {
      selected: {}
    };
  }
  
  getValuesFromKey = (data, key) => {
    return data.map(item => item[key]);
  }
  
  updateSelections = (name, selection) => {
    this.setState({
      selected: {...this.state.selected, [name]: selection}
    }, () => console.log(this.state.selected));
  }

  render() {
    return (
      <div>
        {this.fields.map(field => (
          <Child
            key={field}
            name={field}
            options={this.getValuesFromKey(this.props.data, field)} 
            updateSelections={this.updateSelections}
          />
        ))}
      </div>
    )
  }
}

const data = [
  { firstName: "Jack", status: "Submitted" },
  { firstName: "Simon", status: "Pending" },
  { firstName: "Pete", status: "Approved" },
  { firstName: "Lucas", status: "Rejected" }
];

ReactDOM.render(<Parent data={data}/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>

<div id="root"></div>

person Brett DeWoody    schedule 18.07.2019
comment
Я более чем счастлив помочь указать вам в правильном направлении, но я окажу вам медвежью услугу, если сделаю вашу работу за вас. - person Brett DeWoody; 18.07.2019
comment
Да, если fetch был выполнен в родительском компоненте, то обновления данных могут быть переданы в дочерний компонент. - person Brett DeWoody; 23.07.2019
comment
Но в этом случае значение флажка не будет сохранено правильно. Оно всегда будет сброшено на исходное значение после нажатия кнопки «Применить»? - person VJR08; 23.07.2019
comment
Вам нужно будет использовать контролируемый ввод (объяснение этого см. В документации), чтобы установить атрибут «checked» для каждого ввода в зависимости от состояния. - person Brett DeWoody; 23.07.2019
comment
Проблема, с которой вы столкнулись сейчас, отличается от исходного вопроса, который вы задали (и мы решили). Я вижу, что состояние правильно сохраняет все выбранные параметры в вашей текущей версии. Вы можете объяснить, что не работает? - person Brett DeWoody; 25.07.2019
comment
Это другая проблема, чем мы решали ранее. Отзыв принятого решения по этой причине, я думаю, осуждается, но мы постараемся решить эту проблему быстро. К сожалению, я не могу воспроизвести проблему, которую вы описываете, для меня флажки сохраняют установленное состояние. Не могли бы вы подробнее описать проблему? - person Brett DeWoody; 25.07.2019
comment
@ Brett DeWoody. Проблема, с которой я столкнулся, заключается в том, что после того, как я нажимаю кнопку «Применить» в конкретном раскрывающемся списке, я выполняю вызов API, который принимает эти проверенные значения и предоставляет мне отфильтрованные данные. Теперь, когда в следующий раз я открою тот же раскрывающийся список, мне понадобятся ранее проверенные значения. Не знаю, что вызывает эту проблему - person VJR08; 25.07.2019
comment
Похоже, вы поддерживаете состояние checked в своем дочернем компоненте, и это состояние checked используется для управления значением флажка - checked={this.state.checkboxOptions[i]. Первоначальный this.state.checkboxOptions происходит от свойства options, которое происходит от getValuesFromKey, а внутри getValuesFromKey вы установили свойство checked на false. Таким образом, он всегда устанавливает для атрибута checked значение false. - person Brett DeWoody; 25.07.2019
comment
Вместо установки checked: false вам нужно, чтобы значение checked зависело от выбранных параметров. Если выбран этот вариант, вы хотите checked: true. - person Brett DeWoody; 25.07.2019
comment
Но начальное значение останется неопределенным, не так ли? Было бы действительно полезно, если бы вы могли просто отредактировать фрагмент или дать аналогичное редактирование - person VJR08; 25.07.2019
comment
Я всегда стараюсь научить решать проблему, а не исправлять ее. Значение checked было бы true, если была выбрана эта опция, или false в противном случае. - person Brett DeWoody; 25.07.2019
comment
Вы действительно близки. Просто вы жестко запрограммировали checked: false, который затем передается в флажок и устанавливает checked=false в поле. - person Brett DeWoody; 25.07.2019
comment
@ Brett DeWoody Но здесь я не поддерживаю переменную состояния в родительском праве, которая будет отображать отмеченный объект внутри родительского. Как я прочитаю это от ребенка? - person VJR08; 25.07.2019
comment
Но это то, что вы делаете - у вас есть свойство checked в объекте внутри getValuesFromKey, которое передается дочернему элементу и используется как атрибут checked входных данных. Вот быстрый пример: измените строку 42 файла index.tsx на checked: true, и вы заметите, что все флажки теперь отмечены по умолчанию. - person Brett DeWoody; 25.07.2019
comment
Да, я понял. Теперь вы хотите сказать, что вызовите getColumnFilterValues ​​() после фильтрации, чтобы обновить значение? - person VJR08; 25.07.2019
comment
Проблема в том, что React вызывает getColumnFilterValues() при изменении данных, и поскольку getColumnFilterValues() содержит checked: false, флажки сбрасываются в непроверенное положение. Вам нужно изменить getColumnFilterValues(), чтобы checked: false выглядело как checked: isThisValueSelected ? true : false - person Brett DeWoody; 26.07.2019
comment
Вы также можете удалить checked: false и checked опору из <input /> - person Brett DeWoody; 26.07.2019

Значения вашего флажка теряются только тогда, когда вы скрываете / показываете таблицу, поскольку таблица выходит из DOM, ее состояние и ее дочерние элементы теряются. Когда таблица монтируется в DOM, компонент Child снова монтируется, инициализируя новое состояние, принимая значения флажков из метода getValuesFromKey, который по умолчанию возвращает false, снимая флажки.

return {
  field: keys[0],
  checked: false
};

Stackblitz воспроизводит проблему.

Вы должны установить значения флажков, проверяя объект selectedValues, чтобы увидеть, был ли он выбран.

return {
  field: keys[0],
  checked: this.state.selectedValues[key] && this.state.selectedValues[key].includes(keys[0]),
};
person Munim Munna    schedule 27.07.2019