skip to Main Content

I’m trying to remove an element from the array inside an arrays of objects in React using UseState hook, but struggling to get the result, because the interface does not re-render and the object does not removing from it.

As I know, to get any updates of objects reflected in react UI, I should replace a whole object instead of updating it. I was trying to create a copy of an array and assign it through setDives method inside the function, but nothing.

Here is my code:

Declaring const using useState. I need to delete only specific guides by name ("Lee" or "John" in this example):

const [dives, setDives] = useState([
    { boat: 'Marcelo', divesite: '', guides: ['Lee', 'Jhon'] },
]);
    

Delete function:

function deleteGuide(i, guide) {
    var tempArray = dives;
    tempArray[dives.indexOf(i)].guides = dives[dives.indexOf(i)].guides.filter(
        (e) => e !== guide,
    );
        
    setDives(tempArray);
}

Buttons in interface:

{dives.map((i) => (
        <div>
            {dives[dives.indexOf(i)].guides.map((guide) => (
                <ButtonGroup>
                    <Button>{guide}</Button>
                    <Button onClick={() => deleteGuide(i, guide)}></Button>
                </ButtonGroup>
            ))}
        </div>
    ))}

3

Answers


  1. You correctly said, that you have to create a new copy of the Array, but you are actually not doing it. That’s why it is not working.

    Change this line: setDives(tempArray); to this: setDives([...tempArray]);

    Explanation: When just setting tempArray, you are updating only the reference, which does not change, because tempArray = dives. Since there are no changes, there is no re-render and you do not see any results. By using the spread-operator ..., a completely new Array is constructed and therefor the state changes (which triggers a re-render).

    Further reading: https://react.dev/learn/updating-arrays-in-state

    To make this even more understandable, you can add a console.log before your setDives(tempArray)-statement and take a look at the output:

    console.log(tempArray === dives); //will always return true, so no change
    setDives([...tempArray]);
    
    Login or Signup to reply.
  2. There’s a couple of things I would recommend.

    1. Add ids to your dataset, both for the dive and for the guides (this will mean changing the guides from a name string to an object with an id and name. It helps mitigate some of the warnings that React will give you when you’re mapping over arrays and trying to add keys.

    2. Use those ids as data attributes on the button element. You can pick them up in the button handler and use them to help your filter your dataset.

    const { Fragment, useState } = React;
    
    // For the purposes of this example I'm passing in the data
    // as `config` and initialising it as state
    function Example({ config }) {
    
      const [dives, setDives] = useState(config);
        
      // Your delete button handler
      function handleDeleteGuide(e) {
    
        // Destructure the diveid and the guideid from the
        // button's dataset
        const { diveid, guideid } = e.target.dataset;
    
        // `map` over the dataset
        // If the current dive object's id matches the button's
        // diveid value return a new object that spreads out the
        // current object, and updates the guide array by filtering
        // out the guide whose id doesn't match the button's guideid value.
        // Otherwise, if there is no match, return the dive object
        const updated = dives.map(dive => {
          if (Number(diveid) === dive.id) {
            return {
              ...dive,
              guides: dive.guides.filter(guide => {
                return Number(guideid) !== guide.id;
              })
            };
          }
          return dive;
        });
    
        // Set the new state with the array that our
        // `map` operation provided
        setDives(updated);
      }
    
      // Here I'm iterating over the dive objects
      // and then the guide objects making sure to add
      // the appropriate keys.
      // You can see on the button that there are two data
      // attributes (diveid/guideid) which contain, unsuprisingly,
      // the dive id and the guide id - these are what's picked up
      // in the buttons click handler
      // You may want to split these up into separate components
      // at some point before it gets too messy
      return (
        <section>
          {dives.map(dive => {
            const { id, boat, guides } = dive;
            return (
              <Fragment key={id}>
                <h4>Boat: {boat}</h4>
                <h4>Guides</h4>
                <ul>
                  {guides.map(guide => {
                    const { id: guideId, name } = guide;
                    return (
                      <li key={guideId}>
                        {name}
                        <button 
                          className="delete"
                          type="button"
                          data-diveid={id}
                          data-guideid={guideId}
                          onClick={handleDeleteGuide}
                        >Delete
                        </button>
                      </li>
                    );
                  })}
                </ul>
              </Fragment>
            );
          })}
        </section>
      );
    
    }
    
    const config = [
        { id: 1, boat: 'Marcelo', divesite: '', guides: [{ id: 1, name: 'Lee' }, { id: 2, name: 'Jhon'}] },
        { id: 2, boat: 'Calico', divesite: '', guides: [ { id: 1, name: 'Bruce'}, {id: 2, name: 'Johanna' }] }
    ];
    const node = document.getElementById('root');
    const root = ReactDOM.createRoot(node);
    root.render(<Example config={config} />);
    ul { list-style: none; margin: 0; padding: 0;}
    li:not(:first-child) { margin-top: 0.25rem; }
    .delete { margin-left: 0.25rem; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"></script>
    <div id="root"></div>
    Login or Signup to reply.
  3. You should use the function version of the setState when calling setDives. You can use structuredClone to create a deep copy of the current state.

    Note: You should also use the key prop when calling map.

    const { useCallback, useState } = React;
    
    const uuidv4 = () =>
      ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
        (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
    
    const App = () => {
      const [dives, setDives] = useState([
        { boat: 'Marcelo', divesite: '', guides: ['Lee', 'Jhon'] },
      ]);
      
      const deleteGuide = useCallback((e, i) => {
        const guideToRemove = e.target.closest('.ButtonGroup').dataset.guide;
        console.log(`Removing ${guideToRemove}...`);
        setDives((currentDives) => {
          const copyDives = structuredClone(currentDives);
          copyDives[i].guides = copyDives[i].guides.filter((guide) => guide !== guideToRemove);
          return copyDives;
        });
      }, []);
    
      return (
        <div>
          {dives.map((dive, i) => (
            <div key={uuidv4()} className="DiveInfo">
              {dive.guides.map((guide) => (
                <div key={guide} className="ButtonGroup" data-guide={guide}>
                  <button type="button">{guide}</button>
                  <button type="button" className="DeleteButton"
                    onClick={(e) => deleteGuide(e, i)}></button>
                </div>
              ))}
            </div>
          ))}
        </div>
      );
    };
    
    ReactDOM
      .createRoot(document.getElementById('root'))
      .render(<App />);
    html, body, #root, #root > div {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
    
    #root > div {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    
    .DiveInfo {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 0.25rem;
    }
    
    .ButtonGroup {
      display: flex;
      flex-direction: row;
      justify-content: space-between;
      min-width: 6rem;
    }
    
    .DeleteButton:after {
      content: '0D7';
    }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search