1

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);
Bill
  • 4,614
  • 13
  • 77
  • 132

4 Answers4

2

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);
Community
  • 1
  • 1
moshfiqrony
  • 4,303
  • 2
  • 20
  • 29
  • @Bill This isn't a very correct solution, it mutates the state. Although it clones it later it will cause issues when you try to compare fields like drum.name within previous and current state – Shubham Khatri May 12 '20 at 04:05
  • hmm ok I'm all ears, can you edit the sand box to show how to do this without the mutation? – Bill May 12 '20 at 04:07
  • @Bill Here is a suggestion for you. Never use numeric values for the id. Try to use UUID instead. numeric values as an id and passing it as a key always create a problem. – moshfiqrony May 12 '20 at 04:09
  • Thank you, Md. Moshfiqur Rahman Rony il make this change on your recomendation – Bill May 12 '20 at 04:11
  • you can read this. This helped me a lot - https://stackoverflow.com/a/46735689/9418800 – moshfiqrony May 12 '20 at 04:13
  • @Bill Please check this https://codesandbox.io/s/brave-brahmagupta-4tf50 – Shubham Khatri May 12 '20 at 04:15
  • Shubham Khatri that works, is the array being mutated? Does it solve the concerns of Md. Moshfiqur Rahman Rony ? – Bill May 12 '20 at 04:18
  • Both are the same way. But when you use ```map```. Suppose you have 1000 drums it will iterate 1000 drums for each change. But in my case, it will only update the specific object. I have updated my answers. Used ```index``` instead of ```id``` so that no updating issues come up. – moshfiqrony May 12 '20 at 04:26
  • so using index prevents any mutation? – Bill May 12 '20 at 04:31
  • @Bill By using map mutation issue is solved. So if you were to use class component and if you updated state like in the current answer you will see that the previous state and current state hold the same value on update whereas in using map you won't see that behvaiour – Shubham Khatri May 12 '20 at 04:35
  • As I'm not using a class-based component does this argument still stand up? – Bill May 12 '20 at 04:40
  • I never face any mutation problem. And as I said iI prefer this because it updates only that object not iterate the others. I myself also stop using class-based components. – moshfiqrony May 12 '20 at 04:41
0

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 )}
Bassem
  • 3,582
  • 2
  • 24
  • 47
0

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

wxsm
  • 556
  • 4
  • 14
0

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}
        />
      ))}
...
gdh
  • 13,114
  • 2
  • 16
  • 28