1

Simple delete and update experiment on the list and nested list with React, currently delete doesn't work properly for both the main list and sub list, console.table() and it seems to be caused by the Input component, ie if I use <input value={value}> here the value comes from the parent instead of using local state, delete works. Not sure why? Could it be Button component is a sibling to Input and something is not set up properly? Quick Demo: https://codesandbox.io/s/crud-playgroud-xwrip?file=/src/App3.js

import { useState } from "react";

export default function App3() {
  const [list, setList] = useState([
    { val: "a", sublist: [1, 2] },
    { val: "b", sublist: [8, 4, 5] }
  ]);
  
  console.table(list);
  const add = (newVal)=>{
    setList([...list, { val: newVal, sublist: [] },])
  }

  return (
    <>
      <Input  placeholder={'add list item'} add={add}/><br/>
      {list.map((e, i) => (
        <ListItem key={i} index={i} item={e} list={list} setList={setList} />
      ))}
    </>
  );
}

function ListItem({ index, item, list, setList }) {
  const copy = [...list];
  const del = () => {
    copy.splice(index, 1);
    setList(copy);
  };

  const updateSublist = (newSublist) => {
    copy[index].sublist = newSublist;
    setList(copy);
    // console.log(newSublist);
  };

  const add = (newVal)=>{
    copy[index].sublist.push(newVal)
    setList(copy);
  }

  const update = (newVal) => {
    copy[index].val = newVal;
    setList(copy);
  };


  return (
    <>
      <Input value={item.val} update={update} />
      <Button del={del} />
      <div>
        {item.sublist.map((subE, subI) => (
          <SublistItem
            key={subI}
            subIndex={subI}
            subItem={subE}
            subList={item.sublist}
            updateSublist={updateSublist}
          />
        ))}
      </div>
      <Input placeholder={'add sublist item'} add={add} /><br/>
      <div>--------------</div>
    </>
  );
}

function SublistItem({ subIndex, subItem, subList, updateSublist }) {
  const copy = [...subList];
  const del = () => {
    copy.splice(subIndex, 1);
    updateSublist(copy);
  };
  const update = (newVal) => {
    copy[subIndex] = newVal;
    updateSublist(copy);
  };
  return (
    <>
      <Input value={subItem} update={update} />
      <Button del={del} />
    </>
  );
}

function Button({ del }) {
  return (
    <>
      <button onClick={del}>X</button>
    </>
  );
}

function Input({ placeholder, value, update, add }) {
  
  const [inputVal, setInputVal] = useState(value)
  console.log("value: "+value, "inuptVal: "+inputVal); 
  // console.log(add !== undefined)
  const submit = (e) =>{
    if (e.keyCode === 13){
      if (add !== undefined){
        add(inputVal)
        
      }
    }
  }

  return (
    <>
      <input
        value={inputVal}
        //delete work if value={value}
        placeholder={placeholder}
        onChange={(e) => {
          const newVal = e.target.value
          if (add === undefined){
            update(newVal);
          }
          setInputVal(newVal)
        }}
        onKeyDown={(e)=>{submit(e)}}
      />
    </>
  );
}

skyboyer
  • 22,209
  • 7
  • 57
  • 64
carter x
  • 35
  • 3
  • You can use `filter`. See [remove the object from array](https://stackoverflow.com/a/66521085/2873538). – Ajeet Shah Apr 08 '21 at 14:05
  • Thanks, mate, I eventually got it to work by removing the hook in the `input` component completely, and only take value from the prop, `update(e.target.value)}>` but I am confused when to use local state, any reason why we not supposed to use it here? @AjeetShah – carter x Apr 08 '21 at 14:42
  • 1
    One can keep as many States as one *wants*. But we should think better. We should not create multiple copies (redundancies) of data. Because the biggest pain in that would be - "How to sync all the values?". So, it is better to keep less copies or we should try to create Single Source of Truth. Here is a related concept [Lifting State up](https://reactjs.org/docs/lifting-state-up.html). What is purpose of `Input` component? It is 1. Render input field 2. Send the current value to parent. If you can accomplish this task without State in `Input` component, then, don't have a local state. – Ajeet Shah Apr 08 '21 at 14:56
  • Thanks! I have added another function `add`, and requires a local state to reset input to empty after the enter key, The delete it's still a problem, I guess it might because `delete` is the sibling to `input`, the value passed in from parent and the local inputVal out of sync, still a bit lost on how to fix it, any specific advice? – carter x Apr 08 '21 at 16:08

1 Answers1

0

For everyone else's benefit, I will answer my own question, the problem lies with KEYs, you need key's when you create the element/subelement, it's risky to assign key during using index .map((e,i)=><SomeComponent key={i}/>)because you might end up repeating keys for your elements.

solution: change { val: "a", sublist: [1, 2] }, to

    {
      key: uniqueID(),
      val: "a",
      sublist: [
        { key: uniqueID(), subVal: 1 },
        { key: uniqueID(), subVal: 2 }
      ]
    },

and uniqueID() can be as simple as Math.random() here's the full code for CRUD in a nested list strucutre:

import { useState } from "react";

export default function App3() {

  const [list, setList] = useState([
    {
      key: uniqueID(),
      val: "a",
      sublist: [
        { key: uniqueID(), subVal: 1 },
        { key: uniqueID(), subVal: 2 }
      ]
    },
    {
      key: uniqueID(),
      val: "b",
      sublist: [
        { key: uniqueID(), subVal: 8 },
        { key: uniqueID(), subVal: 4 },
        { key: uniqueID(), subVal: 3 }
      ]
    }
  ]);

  console.table(list);
  const add = (newVal) => {
    setList([...list, { key: uniqueID(), val: newVal, sublist: [] }]);
  };

  return (
    <>
      <Input placeholder={"add list item"} add={add} />
      <br />
      {list.map((e, i) => (
        <ListItem
          key={e.key}
          index={i}
          item={e}
          list={list}
          setList={setList}
        />
      ))}
    </>
  );
}

function ListItem({ index, item, list, setList }) {
  const copy = [...list];
  const del = () => {
    copy.splice(index, 1);
    setList(copy);
  };

  const updateSublist = (newSublist) => {
    copy[index].sublist = newSublist;
    setList(copy);
    // console.log(newSublist);
  };

  const add = (newVal) => {
    copy[index].sublist.push({ key: uniqueID(), subVal: newVal });
    setList(copy);
  };

  const update = (newVal) => {
    copy[index].val = newVal;
    setList(copy);
  };

  return (
    <>
      <Input value={item.val} update={update} />
      <Button del={del} />
      <div>
        {item.sublist.map((subE, subI) => (
          <SublistItem
            key={subE.key}
            subIndex={subI}
            subItem={subE}
            subList={item.sublist}
            updateSublist={updateSublist}
          />
        ))}
      </div>
      <Input placeholder={"add sublist item"} add={add} />
      <br />
      <div>--------------</div>
    </>
  );
}

function SublistItem({ subIndex, subItem, subList, updateSublist }) {
  const copy = [...subList];

  const del = () => {
    copy.splice(subIndex, 1);
    updateSublist(copy);
  };
  const update = (newVal) => {
    copy[subIndex].subVal = newVal;
    updateSublist(copy);
  };
  return (
    <>
      <Input value={subItem.subVal} update={update} />
      <Button del={del} />
    </>
  );
}

function Button({ del }) {
  return (
    <>
      <button onClick={del}>X</button>
    </>
  );
}

function Input({ placeholder, value, update, add }) {
  const [inputVal, setInputVal] = useState(value);
  console.log("value: " + value, "inuptVal: " + inputVal);
  // console.log(add !== undefined)
  const submit = (e) => {
    if (e.keyCode === 13) {
      if (add !== undefined) {
        add(inputVal);
        setInputVal("");
      }
    }
  };

  return (
    <>
      <input
        // key={value}
        value={inputVal}
        //delete work if value={value}
        placeholder={placeholder}
        onChange={(e) => {
          const newVal = e.target.value;
          if (add === undefined) {
            update(newVal);
          }
          setInputVal(newVal);
        }}
        onKeyDown={(e) => {
          submit(e);
        }}
      />
    </>
  );
}


const uniqueID = () => {
  return (((1 + Math.random()) * 0x10000000000000) | 0)
    .toString(32)
    .substr(1);
};

carter x
  • 35
  • 3