Updating state from recursively rendered component in React.js

徘徊边缘 提交于 2021-02-08 05:22:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!