1

My delete callback is sending the correct id but I tested out the reducer with a few console logs and it's not working correctly.So I have a list of components in a menu that I can add to the "board". I notice that if I add only notes for example, it will delete the last one every time. But if I had one note, then one todo list, and another note, if I try to delete the first note, it will delete that one correctly.

reducer

export default function reducer(state: any, action: any) {
  switch (action.type) {
    case "ADD_COMPONENT":
      return {
        ...state,
        components: [...state.components, action.payload],
      };
    case "DELETE_COMPONENT":
      return {
        ...state,
        components: state.components.filter(
          // actual code
          (component: any) =>  component.id !== action.payload),
          //test code to check what ids were being passed in
          //(component: any) => { console.log(component.id, action.payload), console.log(component.id !== action.payload), component.id == action.payload }),
      };

    default:
      return state;
  }
}

The Delete case has a line I was using to test to see what ids were being passed in.

The context

import React, { createContext, useReducer, useState } from "react";
import ComponentReducer from "./ComponentReducer";

const NewComponentState: NewComponentsState = {
  components: [],
  addComponent: () => {},
  deleteComponent: () => {},
};

export const NewComponentContext =
  React.createContext<NewComponentsState>(NewComponentState);

export const NewComponentProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(ComponentReducer, NewComponentState);

  const addComponent = (component: any) => {
    dispatch({
      type: "ADD_COMPONENT",
      payload: component
    });
  };
  const deleteComponent = (id: any) => {
    dispatch({
      type: "DELETE_COMPONENT",
      payload: id,
    });
  };

  return (
    <NewComponentContext.Provider
      value={{ components: state.components, deleteComponent, addComponent }}
    >
      {children}
    </NewComponentContext.Provider>
  );
};

The Note component that I've been using to test this

import { Menu,Transition } from "@headlessui/react";
import React, { useState, Fragment, useContext } from "react";
import NoteColorChanger from "./NoteColor";
import Draggable from 'react-draggable';
import { NewComponentContext } from "../../../Context/NewComponentContext";

interface INote {
  id: any
}
const Note: React.FC <INote> = ({ id }) => {
  const [content, setContent] = useState<string>()
  const [title, setTitle] = useState<string>()
  const [color, setColor] = useState<any>()
  const [position, setPosition] = useState<any>({x: 0, y: 0})

  const { components, deleteComponent } = useContext(NewComponentContext)

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setContent(event.target.value)
  };

  const handleColor = (notecolor: any) => {
    setColor(notecolor)
  }

  const trackPosition = (pos:any) => {
    setPosition({x: pos.x, y: pos.y})
  }

  const deleteNote = () => {
    deleteComponent(id)
  }

  return ( 
    <div className={`${color} h-64 w-64 bg-yellow-200 text-black rounded-lg p-2 shadow-lg`}>
      <div className="flex justify-between items-center pb-6">
        <h1 className="font-bold font-Inter">Note</h1>
        <Menu>
          <Menu.Button>
            <div className={`hover:${color} p-1 rounded-lg ease-in-out duration-100`}>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                className="h-6 w-6"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={1}
                  d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"
                />
              </svg>
            </div>
          </Menu.Button>
          <Transition
            as={Fragment}
            enter="transition ease-out duration-100"
            enterFrom="transform opacity-0 scale-95"
            enterTo="transform opacity-100 scale-100"
            leave="transition ease-in duration-75"
            leaveFrom="transform opacity-100 scale-100"
            leaveTo="transform opacity-0 scale-95"
          >
            <Menu.Items
              as="div"
              className={`bg-gray-100 font-Inter w-64 shadow-lg rounded-lg absolute translate-y-24 -translate-x-2 z-50`}
            >
              <Menu.Item>
                {({ active }) => (
                  <div
                    id="color"
                    className={` flex items-center py-2 px-3 rounded-lg w-full`}
                  >
                    {<NoteColorChanger handleColor={handleColor}/>}
                    
                    
                  </div>
                )}
              </Menu.Item>
              <Menu.Item>
                {({ active }) => (
                  <button
                    id="Todo"
                    onClick={() => console.log(id)}
                    className={`${
                      active ? "bg-blue-500 text-white" : "text-black"
                    } flex items-center py-2 px-3 rounded-lg w-full`}
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6 mr-3"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
                      />
                    </svg>
                    Edit Title
                  </button>
                )}
              </Menu.Item>
              <Menu.Item>
                {({ active }) => (
                  <li
                    onClick={deleteNote}
                    className={`${
                      active ? "bg-blue-500 text-white" : " text-black"
                    } flex items-center py-2 px-3 cursor-pointer rounded-lg`}
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6 mr-3"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
                      />
                    </svg>
                    Delete Note
                  </li>
                )}
              </Menu.Item>
              
            </Menu.Items>
          </Transition>
        </Menu>
      </div>

      <textarea
        className={`${color} bg-yellow-200 font-Inter w-full h-48 border-none focus:border-none focus:ring-0 resize-none`}
        onChange={() => {handleChange}}
      />
    </div>
   
  );
};
export default Note;

Menu where the components are created

import React, { useContext, Fragment, useState } from "react"
import { NewComponentContext } from "../../Context/NewComponentContext"
import { Menu, Transition } from '@headlessui/react'
import Note from "./Note/Note";
import TodoList from "./Todo/TodoList";
import Photo from "./Photo/Photo";


const NewComponentMenu = () => {
    const { addComponent } = useContext(NewComponentContext)

    const newComponent = (componentType: number) => {
        addComponent({id: Math.floor(Math.random() * 100000000), componentType: componentType})
    }
    return (
      <div className="w-6 h-6 mt-4 ml-4 shadow-md text-gray-800 font-Inter z-50">
        <Menu>
          <Menu.Button>
            <div className="p-1 rounded-lg bg-blue-500 hover:bg-blue-600 ease-in-out duration-100">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                className="h-6 w-6 text-blue-50"
                viewBox="0 0 20 20"
                fill="currentColor"
              >
                <path
                  fillRule="evenodd"
                  d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
                  clipRule="evenodd"
                />
              </svg>
            </div>
          </Menu.Button>
          <Transition
            as={Fragment}
            enter="transition ease-out duration-100"
            enterFrom="transform opacity-0 scale-95"
            enterTo="transform opacity-100 scale-100"
            leave="transition ease-in duration-75"
            leaveFrom="transform opacity-100 scale-100"
            leaveTo="transform opacity-0 scale-95"
          >
            <Menu.Items as="div" className="w-60 shadow-lg rounded-lg bg-white">
              <h1 className="text-center font-Inter text-2xl pb-2">Items</h1>
              <Menu.Item>
                {({ active }) => (
                  <button
                    id="Todo"
                    onClick={() => newComponent(1)}
                    className={`${
                      active ? "bg-blue-500 text-white" : "text-black"
                    } flex items-center py-2 px-3 rounded-lg w-full`}
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6 mr-3"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
                      />
                    </svg>
                    Todo list
                  </button>
                )}
              </Menu.Item>
              <Menu.Item>
                {({ active }) => (
                  <li
                    //onClick={() => setComponent(<Moodboard />) }
                    className={`${
                      active ? "bg-blue-500 text-white" : " text-black"
                    } flex items-center py-2 px-3 cursor-pointer rounded-lg`}
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6 mr-3"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                      />
                    </svg>
                    Mood Board
                  </li>
                )}
              </Menu.Item>
              <Menu.Item>
                {({ active }) => (
                  <li
                    onClick={() => newComponent(2) }
                    className={`${
                      active ? "bg-blue-500 text-white" : " text-black"
                    } flex items-center py-2 px-3 cursor-pointer rounded-lg`}
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6 mr-3"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
                      />
                    </svg>
                    Image
                  </li>
                )}
              </Menu.Item>
              <Menu.Item>
                {({ active }) => (
                  <li
                    onClick={() => newComponent(3)}
                    className={`${
                      active ? "bg-blue-500 text-white" : " text-black"
                    } flex items-center py-2 px-3 cursor-pointer rounded-lg`}
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-6 w-6 mr-3"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
                      />
                    </svg>
                    Note
                  </li>
                )}
              </Menu.Item>
            </Menu.Items>
          </Transition>
        </Menu>
      </div>
    );
  };
  
  export default NewComponentMenu;

component where the menu items are created

import React, { useContext } from "react";
import { NewComponentContext } from "../Context/NewComponentContext";
import NewComponentMenu from "./NewComponents/NewComponentMenu";
import Note from "./NewComponents/Note/Note";
import Photo from "./NewComponents/Photo/Photo";
import TodoList from "./NewComponents/Todo/TodoList";

const Board = () => {
    const { components } = useContext(NewComponentContext);

    const componentList  = components.map((component, i) => {
      if (component.componentType == 1) {
        return (
          <div key={i}>
            <TodoList id={component.id} />
          </div>
        );
      }
      else if (component.componentType == 2) {
        return (
          <div key={i}>
            <Photo id={component.id} />
          </div>
        ) 
      }
      else {
        return (
          <div key={i}>
            <Note id={component.id} />
          </div>
        )
        
      }
    });
  
    return (
      <div className="flex space-x-10 mt-8">
        <NewComponentMenu />
        <div className="grid grid-cols-6 gap-8">{componentList}</div>
      </div>
    );
  };
  
  export default Board;
ckam03
  • 43
  • 4
  • Your `filter` callback is lacking a `return`. Also beware of the [comma operator](https://stackoverflow.com/questions/3561043/what-does-a-comma-do-in-javascript-expressions) -- it's better to split statements with `;` – yuriy636 Aug 26 '21 at 17:05

2 Answers2

0

It is really a good practice to use key attribute while rendering a collection, it helps react to rerender collections of components correctly. So i can assume you should try to add unique key attribute to the place, where you render Note component, e.g <Note key={'some unique id'} />

  • Thank you. Yes I've got it set up with a unique id so everything is added correctly but it's not deleting correctly. The correct id is passed in but the last item seems to always get deleted. I can't really figure out why. – ckam03 Aug 26 '21 at 17:12
0

There is an issue with your delete case in reducer. You are not returning the boolean, so no items will be returned,

case "DELETE_COMPONENT":
      return {
        ...state,
        components: state.components.filter(
          (component: any) => { 
              console.log(component.id, action.payload)
              console.log(component.id !== action.payload)
              return component.id !== action.payload // here is the fix
            })
      };
  • I probably should've clarified but my actual code is (component: any) => component.id !== action.payload) and the way I have it was just to test what was being passed in. I don't need to return anything with the way I have it here, correct? – ckam03 Aug 26 '21 at 17:12
  • OK. so now what is the exact issue then? You mentioned its working but haven't clearly specified _what_ is not working? I also now noticed the `components` that you get from the context is not used anywhere in Note component. Am I missing something here? Please let me know to assist better. – Emmanuel Ponnudurai Aug 26 '21 at 17:16
  • Components context doesn't need to be there. I was just trying stuff and never took that out. I can create new Notes just fine. Each have a unique id but the issue is that if I go to delete the first note created or even the 2nd note, the last note is always deleted first. Doesn't matter how many there are, the last note is always deleted. The console logs in the delete case show that the correct id is passed in but somehow the last note is always what ends up being deleted. I don't really understand why. – ckam03 Aug 26 '21 at 17:19
  • When you say deleted. Deleted from where exactly? So are you saying that if you have `[{id: 1}, {id: 2}, {id: 3}]`. No matter what you do, always last item is remove and you have `[{id: 1}, {id: 2}]`? – Emmanuel Ponnudurai Aug 26 '21 at 17:50
  • Can you clearly say where are you deleting from? How are you calling the delete dispatch. Another thing to make sure if the types. if id is of type string and the incoming type is number, doing === or !=== will result in fasly values and may throw you off, so that is another thing you need to double check – Emmanuel Ponnudurai Aug 26 '21 at 17:51
  • Yes exactly, last item is always removed. So I've got a menu in the Note component, which calls the deleteNote function via onclick event handler. Then I just pass the id to the deleteComponent callback. Delete dispatch is inside context. – ckam03 Aug 26 '21 at 17:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236456/discussion-between-emmanuel-ponnudurai-and-ckam03). – Emmanuel Ponnudurai Aug 26 '21 at 19:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236467/discussion-between-ckam03-and-emmanuel-ponnudurai). – ckam03 Aug 27 '21 at 00:23