Bill
Bill

Reputation: 5150

React: How to update an object in an array from a child component

When a new drum is added to the array of drums in setDrums each drum component is displayed, the user can give the drum a name, how do I add the name to the drum in the array drums?

I can logout the drum id, but how do I find that drum in the array and only update that drum with the name the user entered?

https://codesandbox.io/s/amazing-dan-dsudq?file=/src/index.js:0-1371

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const rootElement = document.getElementById("root");

const Drum = ({ id, count, remove, editName, name }) => {
  const [sounds, setSounds] = useState(count);

  useEffect(() => {
    if (sounds > 0)
      setTimeout(() => {
        console.log("Playing sound");
        setSounds(sounds - 1);
      }, 1000);
  }, [sounds]);

  return (
    <div>
      <p>Drum #{id}</p>
      <p>Drum Name {name}</p>
      <p>Remaining sounds: {sounds}</p>
      <label>
        Drum Name <input type="text" onChange={editName} />
      </label>
      <br />
      <button onClick={remove}>Delete drum #{id}</button>
    </div>
  );
};

const App = () => {
  const [drums, setDrums] = useState([]);
  const [nextId, setNextId] = useState(0);

  return (
    <div>
      {drums.map(drum => (
        <Drum
          key={drum.id}
          id={drum.id}
          count={drum.count}
          remove={() => setDrums(drums.filter(other => drum.id !== other.id))}
          editName={() => console.log(drum.id)} // <== how to save the entered name to setDrums?
          name={drum.name}
        />
      ))}
      <button
        onClick={() => {
          setDrums([
            ...drums,
            { id: nextId, count: Math.floor(Math.random() * 100) }
          ]);
          setNextId(nextId + 1);
        }}
      >
        Add drum
      </button>
    </div>
  );
};

ReactDOM.render(<App />, rootElement);

Upvotes: 1

Views: 59

Answers (4)

moshfiqrony
moshfiqrony

Reputation: 4723

Updated

I have updated the codesnadbox also.Link

Theory - To update the value we need an identifier of the array, which is the index of each drum.

I created an editDrumName function which accepts two parameters one is the event and the other is the id. Then I cloned the drums in a tempDrums variable and updated the value with the id.

You cannot do this to the child component as because the value passed in the props.

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const rootElement = document.getElementById("root");

const Drum = ({ id, count, remove, editName, name, index }) => {
  const [sounds, setSounds] = useState(count);

  useEffect(() => {
    if (sounds > 0)
      setTimeout(() => {
        console.log("Playing sound");
        setSounds(sounds - 1);
      }, 1000);
  }, [sounds]);

  return (
    <div>
      <p>Drum #{id}</p>
      <p>Drum Name: {name}</p>
      <p>Remaining sounds: {sounds}</p>
      <label>
        Drum Name <input type="text" onChange={e => editName(e, index)} />
      </label>
      <br />
      <button onClick={remove}>Delete drum #{id}</button>
    </div>
  );
};

const App = () => {
  const [drums, setDrums] = useState([]);
  const [nextId, setNextId] = useState(0);

  const editDrumName = (event, id) => {
    let tempDrums = drums;
    tempDrums[id].name = event.target.value;
    setDrums([...tempDrums]);
  };

  return (
    <div>
      {drums.map((drum, index) => (
        <Drum
          key={drum.id}
          id={drum.id}
          index-{index}
          count={drum.count}
          remove={() => setDrums(drums.filter(other => drum.id !== other.id))}
          editName={editDrumName} // <== how to save the entered name to setDrums?
          name={drum.name}
        />
      ))}
      <button
        onClick={() => {
          setDrums([
            ...drums,
            { id: nextId, count: Math.floor(Math.random() * 100) }
          ]);
          setNextId(nextId + 1);
        }}
      >
        Add drum
      </button>
    </div>
  );
};

ReactDOM.render(<App />, rootElement);

Upvotes: 2

gdh
gdh

Reputation: 13682

The correct way is to map thru the array, find the drum and update it.

We shouldn't mutate state directly. With objects, when we copy it and update a property of copied object, original object is mutated.

working demo is here

Like this

...
const editName = (e, id) => {
    setDrums(
      drums.map(item => {
        if (item.id === id) {
          return { ...item, name: e.target.value };
        }
        return item;
      })
    );
  };
...
    {drums.map(drum => (
        <Drum
          key={drum.id}
          id={drum.id}
          count={drum.count}
          remove={() => setDrums(drums.filter(other => drum.id !== other.id))}
          editName={e => editName(e, drum.id)} // <== how to save the entered name to setDrums?
          name={drum.name}
        />
      ))}
   {drums.map(drum => (
        <Drum
          key={drum.id}
          id={drum.id}
          count={drum.count}
          remove={() => setDrums(drums.filter(other => drum.id !== other.id))}
          editName={e => editName(e, drum.id)} // <== how to save the entered name to setDrums?
          name={drum.name}
        />
      ))}
...

Upvotes: 0

wxsm
wxsm

Reputation: 586

Since you can't edit the original array directly because react state should be treat as immutable, you should make a new referance of the array on every change.

editName={event => {
  // set the new value to drum
  drum.name = event.target.value;
  // update drums state by creating shallow copy of the old one
  setDrums(drums.slice(0));
}}

ref: http://facebook.github.io/react/docs/component-api.html

Upvotes: 0

Bassem
Bassem

Reputation: 4030

What about changing the array to an object then call the setDrums like this:

editName={(e) => setDrums({...drums, drums[drum.id].name: e.target.value )}

Upvotes: 0

Related Questions