javascript - 当父组件内部发生更改时,子组件的本地状态会更改:ReactJS
问题描述
我有一种情况,我正在为反应中的表实现自定义下拉过滤器。我为每列设置了一组下拉值,并且有一个应用按钮。
我为此维护了一个子组件,它接受下拉值并将选定的值发送回父级。
过滤发生了,但是当我再次打开此下拉列表时,复选框值会丢失。
有人可以告诉我哪里出错了吗?
我被困在这里
沙盒: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>
);
}
}
解决方案
对不起,我下面的答案是错误的。
在您的主要组件中,您只设置一次 state.columns,当组件确实安装时。以后永远不会改变。
但是每次您的 state.data 发生更改时,您的主要组件都会重新渲染,并且该ReactTable
组件也会使用新数据和您的原始 state.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>
);
}
您仍然必须使用selectedOption
Child 组件中的道具来预先检查您的复选框。
旧的(和错误的)答案:
当您获得过滤数据并使用它设置您的状态时,现在您的 state.data 已更改,它将重新渲染从它派生的道具的子组件。
这会将您的 Child 组件的状态重新初始化为其{selected: []}
值。
为防止这种情况,您应该通过仅将状态保留在主组件中并通过道具传递所选选项的信息来使您的子组件成为“受控组件”:https ://reactjs.org/docs/forms.html#受控组件
推荐阅读
- pelican - 如何让 Pelican 使用 save_as 元数据来填充规范的 og:url 头部信息?
- apache-kafka - 手动设置 Kafka 消费者偏移量
- keras - 如何在 Keras 中重新初始化图层而不是权重
- regex - 提取超链接 Google Apps 脚本
- c# - iOS自定义渲染不应用角半径
- reactjs - 使用钩子从道具中反应材料表编辑
- c# - 在 EF Core 中使用语法在 dbcontext 内部或外部启动对象是否重要?
- php - PHP while loop + foreach 在同一页面上加倍结果
- javascript - 用于替换 Twitter 单词的 Chrome 扩展
- python-3.x - 从 AWS config 自动修复操作中排除 s3 存储桶