当父组件内部发生更改时,子组件的局部状态也会更改:ReactJS

时间:2019-07-24 09:31:07

标签: javascript reactjs react-hooks setstate react-table

我遇到一种情况,我正在为React中的表实现自定义下拉过滤器。我为每列设置了下拉值,并且有一个“应用”按钮。

为此,我维护了一个子组件,该组件接收下拉值并将选定的子组件发送回父组件。

发生过滤,但是当我再次打开此下拉列表时,复选框值丢失了。

有人可以告诉我我要去哪里了吗

我被困在这里

沙盒: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>
    );
  }
}




1 个答案:

答案 0 :(得分:0)

对不起,我在下面的回答是错误的。

在主要组件中,安装组件后,只需设置一次state.columns。此后再也不会更改。

但是,每次更改state.data时,将使用新数据 AND 并使用原始状态重新渲染主组件,并重新渲染ReactTable组件.columns,其中没有有关所选选项的信息。

我认为您应该在每次更新后重建列对象,并向子组件传递selectedOption之类的支持。

您的子道具取决于您的state.selectedValues和state.data。 您可以在render()中重建列对象。我正在使用buildColumns方法从渲染中获取细节。

您在您的州不需要columns属性。

...
constructor(props) {
    super(props);
    this.state = {
      data: [
        { firstName: "Jack", status: "Submitted", age: "14" },
        { firstName: "Simon", status: "Pending", age: "15" },
        { firstName: "Pete", status: "Approved", age: "16" },
        { firstName: "Lucas", status: "Rejected", age: "19" }
      ],
      selectedValues: {},
    };
  }
...
buildColumns = ({ data, selectedValues = {} }) => {
    const { firstName, status } = selectedValues
    return [
      {
        Header: () => (
          <div>
            <div style={{ position: "absolute", marginLeft: "10px" }}>
              <Child
                key="firstName"
                name="firstName"
                options={this.getValuesFromKey(data, "firstName")}
                selectedOption={firstName}
                handleFilter={this.handleFilter}
              />
            </div>
            <span>First Name</span>
          </div>
        ),
        accessor: "firstName",
        sortable: false,
        show: true,
        displayValue: " First Name"
      },
      {
        Header: () => (
          <div>
            <div style={{ position: "absolute", marginLeft: "10px" }}>
              <Child
                key="status"
                name="status"
                options={this.getValuesFromKey(data, "status")}
                selectedOption={status}
                handleFilter={this.handleFilter}
              />
            </div>
            <span>Status</span>
          </div>
        ),
        accessor: "status",
        sortable: false
      },
      {
        Header: "Age",
        accessor: "age"
      }
    ];
  }

  render() {
    const { data, selectedValues } = this.state;
    const columns = this.buildColumns({ data, selectedValues })
    return (
      <div>
        <ReactTable
          data={data}
          columns={columns}
          defaultPageSize={10}
          className="-striped -highlight"
        />
      </div>
    );
  }

您仍然必须使用Child组件内的selectedOption道具来预先选中您的复选框。


旧的(和错误的)答案:

获取过滤数据并设置状态后,现在state.data已更改,它将重新渲染Child组件,该组件将从中派生道具。

这会将子组件的状态重新初始化为其{selected: []}值。

为防止这种情况,您应通过仅将状态保留在主组件中,并通过道具传递所选选项的信息来使子组件成为“受控组件”:https://reactjs.org/docs/forms.html#controlled-components