Ycon
Ycon

Reputation: 1950

Nested items in setState (non-mutative)

I am trying to (without mutating state) update a value that is nested.

The result should go from: [{ name: "hello", id:20, genre: [{ name: 'baz', id: 2 }, { name: 'foo', id: 200 }] }, { name: "hi", id:12, genre: [] }]

to [{ name: "hello", id:20, genre: [{ name: 'test', id: 2 }, { name: 'foo', id: 200 }] }, { name: "hi", id:12, genre: [] }]

I have located the index of the item I want to update- nestedIndex. Although, matching using ID would also be find (if index is not a good idea).

I attempted to it here:

return item.name === x.name ? { ...item, name: e.target.value, genre[nestedIndex].name: 'test' } : item

How do I update a nested item in setState?

Here is a working example: https://codesandbox.io/s/rl6yoyz1rm

Here is my full code:

  state = {
    search: "",
    items: [{ name: "hello", id:20, genre: [{ name: 'baz', id: 2 }, { name: 'foo', id: 200 }] }, { name: "hi", id:12, genre: [] }]
  };

  onChange(e, x) {
    const { items } = this.state
    const index = items.findIndex(itm => itm.name === x.name)
    const nestedIndex = [items[index]].map(e => e.genre).pop().findIndex(itm => itm.id === 2)
    this.setState({
      items: items.map(item => {
        return item.name === x.name ? { ...item, name: e.target.value } : item
      })
    });
  }

  render() {
    const { items } = this.state;
    return (
      <div>
        {items.map((x) => {
          return (
            <div>
              {" "}
              {x.name}
              {x.genre.map((itm) => {
                return <i> {itm.name}</i>;
              })}
              <input onChange={e => this.onChange(e, x)} type="text" />
            </div>
          );
        })}
      </div>
    );
  }
}

Upvotes: 0

Views: 50

Answers (1)

Hemerson Carlin
Hemerson Carlin

Reputation: 7424

Since you are updating an array which also happens to have another nested array, you can map through items and spread props as you need.

class App extends React.Component {
  constructor() {
    super()

    this.state = {
      items: [{
        name: 'hello',
        id: 20,
        genre: [{ name: 'baz', id: 2 }, { name: 'foo', id: 200 }],
      },
      { name: 'hi', id: 12, genre: [] },]
    }

    this.onChange = this.onChange.bind(this)
  }

  onChange(event, clickedItem) {
    const { items } = this.state
    const index = items.findIndex(item => item.name === clickedItem.name)
    const nestedIndex = items[index].genre.findIndex(genre => genre.id === 2)
    
    const { value } = event.target
    this.setState(prevState => ({
      items: prevState.items.map((item, itemIndex) => {
        if (index !== itemIndex) {
          return item
        }
    
        return {
          ...item,
          genre: item.genre.map((genre, gIndex) => {
            if (gIndex !== nestedIndex) {
              return genre  
            }
    
            return {
              ...genre,
              name: value,
            }
          })
        }
      })
    }))
  }

  render() {
    const { items } = this.state;
    return (
      <div>
        {items.map((x) => {
          return (
            <div>
              {" "}
              {x.name}
              {x.genre.map((itm) => {
                return <i> {itm.name}</i>;
              })}
              <input onChange={e => this.onChange(e, x)} type="text" />
            </div>
          );
        })}
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Upvotes: 1

Related Questions