Hamza Haddad
Hamza Haddad

Reputation: 1556

Deleting list Item in react keep older items instead

I would like to delete selected item from list.

When I click on delete the right item get deleted from the list content but on UI I get always the list item fired.

I seems to keep track of JSX keys and show last values.

Here's a demo

const Holidays = (props) => {
  console.log(props);
  const [state, setState] = useState({ ...props });
  useEffect(() => {
    setState(props);
    console.log(state);
  }, []);
  const addNewHoliday = () => {
    const obj = { start: "12/12", end: "12/13" };
    setState(update(state, { daysOffList: { $push: [obj] } }));
  };
  const deleteHoliday = (i) => {
    const objects = state.daysOffList.filter((elm, index) => index != i);

    console.log({ objects });
    setState(update(state, { daysOffList: { $set: objects } }));
    console.log(state.daysOffList);
  };
  return (
    <>
      <Header as="h1" content="Select Holidays" />
      <Button
        primary
        icon={<AddIcon />}
        text
        content="Add new holidays"
        onClick={() => addNewHoliday(state)}
      />

      {state?.daysOffList?.map((elm, i) => {
        console.log(elm.end);
        return (
          <Flex key={i.toString()} gap="gap.small">
            <>
              <Header as="h5" content="Start Date" />
              <Datepicker
                defaultSelectedDate={
                  new Date(`${elm.start}/${new Date().getFullYear()}`)
                }
              />
            </>
            <>
              <Header as="h5" content="End Date" />
              <Datepicker
                defaultSelectedDate={
                  new Date(`${elm.end}/${new Date().getFullYear()}`)
                }
              />
            </>
            <Button
              key={i.toString()}
              primary
              icon={<TrashCanIcon />}
              text
              onClick={() => deleteHoliday(i)}
            />
            <span>{JSON.stringify(state.daysOffList)}</span>
          </Flex>
        );
      })}
    </>
  );
};
export default Holidays;

enter image description here

Update

I'm trying to make a uniq id by adding timeStamp.

    return (
      <Flex key={`${JSON.stringify(elm)} ${Date.now()}`} gap="gap.small">
        <>
          <Header as="h5" content="Start Date" />
          <Datepicker
            defaultSelectedDate={
              new Date(`${elm.start}/${new Date().getFullYear()}`)
            }
          />
        </>
        <>
          <Header as="h5" content="End Date" />
          <Datepicker
            defaultSelectedDate={
              new Date(`${elm.end}/${new Date().getFullYear()}`)
            }
          />
        </>
        <Button
          primary
          key={`${JSON.stringify(elm)} ${Date.now()}`}
          icon={<TrashCanIcon />}
          text
          onClick={() => deleteHoliday(i)}
        />{" "}
      </Flex>
    );

I was hoping that the error disappear but still getting same behaviour

Upvotes: 0

Views: 607

Answers (1)

Drew Reese
Drew Reese

Reputation: 202618

Issue

You are using the array index as the React key and you are mutating the underlying data array. When you click the second entry to delete it, the third element shifts forward to fill the gap and is now assigned the React key for the element just removed. React uses the key to help in reconciliation, if the key remains stable React bails on rerendering the UI.

You also can't console log state immediately after an enqueued state update and expect to see the updated state.

setState(update(state, { daysOffList: { $set: objects } }));
console.log(state.daysOffList);

React state updates are asynchronous and processed between render cycles. The above can, and will, only ever log the state value from the current render cycle, not the update enqueued for the next render cycle.

Solution

Use a GUID for each start/end data object. uuid is a fantastic package for this and has really good uniqueness guarantees and is incredibly simple to use.

import { v4 as uuidV4 } from 'uuid';

// generate unique id
uuidV4();

To specifically address the issues in your code:

  • Add id properties to your data

     const daysOffList = [
       { id: uuidV4(), start: "12/12", end: "12/15" },
       { id: uuidV4(), start: "12/12", end: "12/17" },
       { id: uuidV4(), start: "12/12", end: "12/19" }
     ];
    
     ...
    
     const addNewHoliday = () => {
       const obj = {
         id: uuidV4(),
         start: "12/12",
         end: "12/13",
       };
       setState(update(state, { daysOffList: { $push: [obj] } }));
     };
    
  • Update handler to consume id to delete

     const deleteHoliday = (id) => {
       const objects = state.daysOffList.filter((elm) => elm.id !== id);
       setState(update(state, { daysOffList: { $set: objects } }));
     };
    
  • Use the element id property as the React key

     {state.daysOffList?.map((elm, i) => {
       return (
         <Flex key={elm.id} gap="gap.small">
           ...
         </Flex>
       );
     })}
    
  • Pass the element id to the delete handler

     <Button
       primary
       icon={<TrashCanIcon />}
       text
       onClick={() => deleteHoliday(elm.id)}
     />
    
  • Use an useEffect React hook to log any state update

     useEffect(() => {
       console.log(state.daysOffList);
     }, [state.daysOffList]);
    

Demo

Edit deleting-list-item-in-react-keep-older-items-instead

Note: If you don't want (or can't) install additional 3rd-party dependencies then you can roll your own id generator. This will work in a pinch but you should really go for a real proven solution.

const genId = ((seed = 0) => () => seed++)();
genId(); // 0
genId(); // 1

Upvotes: 1

Related Questions