问题
I am in need of updating deeply nested object in a React state from a recursively rendered component. The items look like this and can be nested dynamically:
const items = [
{
id: "1",
name: "Item 1",
isChecked: true,
children: []
},
{
id: "3",
name: "Item 3",
isChecked: false,
children: [
{
id: "3.1",
name: "Child 1",
isChecked: false,
children: [
{
id: "3.1.1",
name: "Grandchild 1",
isChecked: true,
children: []
},
{
id: "3.1.2",
name: "Grandchild 2",
isChecked: true,
children: []
}
]
},
{
id: "3.2",
name: "Child 2",
isChecked: false,
children: []
}
]
}
]
I have a problem figuring out how to update the top level state from within a deeply nested component, because it only "knows" about itself, not the entire data structure.
class App extends React.Component {
state = {
items
};
recursivelyRenderItems(items) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<input type="checkbox" checked={item.isChecked} onChange={(event) => {
// TODO: Update the item's checked status
}} />
{this.recursivelyRenderItems(item.children)}
</li>
))}
</ul>
)
}
render() {
return (
<div>
{this.recursivelyRenderItems(this.state.items)}
</div>
);
}
}
How can I achieve this, please?
回答1:
This works in your fiddle you posted.
Basically, each component needs to know about its item.isChecked
and its parent's isChecked
. So, create a component that takes 2 props, item
and parentChecked
where the latter is a boolean from the parent and the former becomes an mutable state variable in the constructor.
Then, the component simply updates its checked state in the event handler and it all flows down in the render method:
import React from "react";
import { render } from "react-dom";
import items from "./items";
class LiComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
item: props.item
}
}
render() {
var t = this;
return (
<ul>
<li key={t.state.item.id}>
{t.state.item.name}
<input
type="checkbox"
checked={t.state.item.isChecked || t.props.parentChecked}
onChange={event => {
t.setState({item: {...t.state.item, isChecked: !t.state.item.isChecked}})
}}
/>
</li>
{t.state.item.children.map(item => (
<LiComponent item={item} parentChecked={t.state.item.isChecked}/>
))}
</ul>
);
}
}
class App extends React.Component {
state = {
items
};
render() {
return (
<div>
{this.state.items.map(item => (
<LiComponent item={item} parentChecked={false} />
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
https://codesandbox.io/s/treelist-component-ywebq
回答2:
How about this:
Create an updateState
function that takes two args: id
and newState
. Since your ids
already tell you what item you are updating: 3, 3.1, 3.1.1
, you can from any of the recursive items call your update function with the id
of the item you want to modify. Split the id
by dots and go throw your items recursively to find out the right one.
For example from the recursive rendered item 3.1.1
, can call updateState
like this: updateState('3.1.1', newState)
.
import React from "react";
import { render } from "react-dom";
import items from "./items";
class App extends React.Component {
state = {
items
};
updateState = item => {
const idArray = item.id.split(".");
const { items } = this.state;
const findItem = (index, children) => {
const id = idArray.slice(0, index).join(".");
const foundItem = children.find(item => item.id === id);
if (index === idArray.length) {
foundItem.isChecked = !item.isChecked;
this.setState(items);
return;
} else {
findItem(index + 1, foundItem.children);
}
};
findItem(1, items);
};
recursivelyRenderItems(items) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<input
type="checkbox"
checked={item.isChecked}
onChange={event => this.updateState(item)}
/>
{this.recursivelyRenderItems(item.children)}
</li>
))}
</ul>
);
}
render() {
return <div>{this.recursivelyRenderItems(this.state.items)}</div>;
}
}
render(<App />, document.getElementById("root"));
Working example.
来源:https://stackoverflow.com/questions/60566919/updating-state-from-recursively-rendered-component-in-react-js