简体   繁体   English

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

[英]Child component's local state changes when change happens inside parent component : ReactJS

I have a situation where I am implementing custom dropdown filter for a table in react. 我有一种情况,我正在为React中的表实现自定义下拉过滤器。 I have set of dropdown values for each column and there is a Apply button. 我为每列设置了下拉值,并且有一个“应用”按钮。

I have maintained a child component for this which takes in drop down values and sends the selected one's back to parent. 我为此维护了一个子组件,该组件接收下拉值并将选定的子组件发送回父组件。

The filtering happens but when I open this dropdown again the checkbox values gets lost. 进行过滤,但是当我再次打开此下拉列表时,复选框值丢失了。

Can someone tell where I am going wrong? 有人可以告诉我我要去哪里错吗?

I am stuck here 我被困在这里

Sand box: https://codesandbox.io/s/nervous-elgamal-0zztb 沙盒: https//codesandbox.io/s/nervous-elgamal-0zztb

I have added the sandbox link with proper comments. 我在沙盒链接中添加了适当的注释。 Please have a look. 请看一看。 I am a bit new to react. 我有点新反应。

Help would be really appreciated 帮助将不胜感激

Parent 父级

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);

Child 儿童


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>
    );
  }
}




Sorry, my here below answer was wrong. 抱歉,我在下面的答案是错误的。

In your main component, you set your state.columns just once, when the component did mount. 在主要组件中,安装组件时只需设置一次state.columns。 It will never be changed after. 此后将永远不会更改。

But each time your state.data get changed, your main component get re-rendered and the ReactTable component get re-rendered as well with the new data AND with your original state.columns which get no info about the selected options. 但是,每次更改state.data时,都会使用新数据以及原始的state.columns来重新呈现主要组件,并重新呈现ReactTable组件而原始的state.columns不会获得有关所选选项的信息。

I think you should reconstruct your column object after each update, passing in your Child components a prop like selectedOption . 我认为您应该在每次更新后重建您的列对象,并向您的Child组件传递一个如selectedOption的道具。

Your child props depend on your state.selectedValues and state.data. 您的孩子道具取决于您的state.selectedValues和state.data。 You can reconstruct your column object in your render(). 您可以在render()中重建列对象。 I'm using a method buildColumns to get the details out of render. 我正在使用buildColumns方法获取渲染的细节。

You don't need the columns property in your state. 您无需在状态中使用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>
    );
  }

You still have to use the selectedOption props inside your Child component to precheck your checkbox. 您仍然必须使用Child组件中的selectedOption道具来预先选中您的复选框。


old (and wrong) answer: 旧的(和错误的)答案:

When you get filtered data and set your state with it, now that your state.data has changed, it will re-render the Child component which get props derived from it. 当您获取过滤后的数据并设置状态时,现在state.data已更改,它将重新渲染Child组件,该组件将从其派生道具。

This will reinitialize the state of your Child component to its {selected: []} value. 这会将子组件的状态重新初始化为其{selected: []}值。

To prevent that, you should make your Child component a 'controlled component' by keeping your state only in your Main component, and passing the info of the selected option by props: https://reactjs.org/docs/forms.html#controlled-components 为了避免这种情况,您应该通过仅将状态保留在主组件中,并通过prop传递所选选项的信息来使子组件成为“受控组件”: https : //reactjs.org/docs/forms.html#受控成分

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

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