React Context API context API issues , when adding and removing an items from UI

I have this working code link, that have some issues with the following.

Adding items

I have multi components called row.. I need to add a single item to each of them, problems is the items is getting added to every one of the rows, instead of the selected one? is the context API the issue or should I use redux, or some other solution for that ? react mind set is really different when it comes to state, any idea how to achieve adding an item to every single row separately?

Removing a single element..

to say the truth I have no access to the id I tried to uses UUID generator, and pass the id down using props, which was a very stupid idea since it regenerate a different id each time, any idea how can I target the id of the parent element,

in the icon component so I can use the filter method on that state and remove the selected item from the items array ??

hiding an icon after click outside of the item …

if you have noticed once you click on an item it shows an icon, what if I decided on not doing any thing and remove the icon ? I tried removing it from the item it self element by using onBlur event but it resulted in the icon (losing the a passed down functionality) so I wont be able to remove the item later on.. currently the icon will disappear but then icon functionality (remove the current element has gone down)

Edit react-context-api-context-api-issues-when-adding-and-removing-an-items-from-ui

// Context Module
import React, { useReducer } from 'react'
import appContext from './appContext'
import AppReducer from './appReducer'
import Item from '../../components/Item';
// the action to chanhe the state !!! 
import {
    ICONS, 
    ITEMS, 
    ADD_ITEM,
    REMOVE_ITEM
    
}  from '../types'
// Creating the state  MUST BE A CAPITAL OTHER WISE IT WILL B^&% !! AND REFUSE TO COMPILE
const AppState = (props) => {
const initialState = {
    Icons : false,
    Items : [<Item/>],
}

const [state, dispatch] = useReducer(AppReducer, initialState)
const Iconsshow = () => {
    // show the icon 
    dispatch({
        type: ICONS,
        stabelIcons : true
    })
}

const Iconshide = () => {
    // show the icon 
    dispatch({
        type: ICONS,
        stabelIcons : false
    })
}

const AddItem = ()=> {
    dispatch({
    type: ADD_ITEM,
    Items : [...state.Items, initialState.Items]
})
}

const RemoveItem = (e)=> {
    dispatch({
    type: REMOVE_ITEM,
    Items : [[<Item/>]]
})
}

return <appContext.Provider
value={{ 
    Icons : state.Icons,
    Items : state.Items,
    AddItem,
    RemoveItem
    }}>
{props.children}
</appContext.Provider>
}

export default AppState;

// Reducer Module

import {
    ICONS_SHOW,
    ICONS_HIDE,
    ITEMS,
    ADD_ITEM,
    REMOVE_ITEM
} from "../types"

export default (state, action ) => {

switch(action.type) {
    case ICONS_SHOW:
    return {
        ...state,
        Icons: true
    } 
    case ICONS_HIDE:
    return {
        ...state,
        Icons: false
    }
    case ITEMS:
    return {
        ...state,
        Items: action.Items
    } 
    case ADD_ITEM:
        return {
        ...state,
        Items: [...state.Items, action.payload]
     
    }
    case REMOVE_ITEM:
        return {
        ...state,
        Items: action.payload
    }
default: 
return state
}
}

code box dependencies

"dependencies": {
      "@fortawesome/fontawesome-svg-core": "^1.2.36",
      "@fortawesome/free-brands-svg-icons": "^5.15.4",
      "@fortawesome/free-regular-svg-icons": "^5.15.4",
      "@fortawesome/free-solid-svg-icons": "^5.15.4",
      "@fortawesome/react-fontawesome": "^0.1.15",
      "@testing-library/jest-dom": "^5.14.1",
      "@testing-library/react": "^11.2.7",
      "@testing-library/user-event": "^12.8.3",
      "moment-timezone": "^0.5.33",
      "node-sass": "^6.0.1",
      "react": "^17.0.2",
      "react-dom": "^17.0.2",
      "react-live-clock": "^5.2.0",
      "react-modal": "^3.14.3",
      "react-moment": "^1.1.1",
      "react-scripts": "4.0.3",
      "uuid": "^8.3.2",
      "web-vitals": "^1.1.2"
  },

Answer

After a day or so tinkering around with your sandbox I decided it had enough going against it that it needed more of a rewrite. The biggest issue was the storing of <Item /> component JSX into the component state. Storing JSX into component state is anti-pattern in React.

Solution

Give each row component an id that can be used to identify it in state. Pass the row id with each dispatched action to add/remove items from that row’s data.

Adding items

appContext – Convert the items array to a rows object

const CentContext = createContext({
  Icons: null,
  rows: {},
  addItem: () => {},
  removeItem: () => {}
});

appReducer – When adding items store only a new element id. When removing items used the passed element id to filter the row’s data.

import { v4 as uuidV4 } from "uuid";

...

export default (state, action) => {
  switch (action.type) {
    ...

    case ADD_ITEM:
      console.log("add item");
      return {
        ...state,
        rows: {
          ...state.rows,
          [action.rowId]: [
            ...(state.rows[action.rowId] ?? []),
            { id: uuidV4() }
          ]
        }
      };

    case REMOVE_ITEM:
      return {
        ...state,
        rows: {
          ...state.rows,
          [action.rowId]: state.rows[action.rowId]?.filter(
            (el) => el.id !== action.elementId
          )
        }
      };
      
    default:
      return state;
  }
};

appState – Swap the items state for a rows object state. Update the add/remove item action creators to consume the appropriate row and element ids. Pass the rows state to the context provider.

const AppState = (props) => {
  const initialState = {
    Icons: false,
    rows: {}
  };

  const [state, dispatch] = useReducer(AppReducer, initialState);

  ...

  const addItem = (rowId) => {
    dispatch({
      type: ADD_ITEM,
      rowId,
    });
  };

  const removeItem = (rowId, elementId) => {
    dispatch({
      type: REMOVE_ITEM,
      rowId,
      elementId,
    });
  };

  return (
    <appContext.Provider
      value={{
        Icons: state.Icons,
        Items: state.Items,
        rows: state.rows,
        addItem,
        removeItem
      }}
    >
      {props.children}
    </appContext.Provider>
  );
};

App – Give each Row component an id prop to uniquely identify it.

<AppState>
  <Row id={1} />
  <Row id={2} />
  <Row id={3} />
</AppState>

Row – Pass the rows id prop to the addItem handler. Map the rows state to the Item component.

function Row(props) {
  // getting the context needed
  const context = useContext(appContext);
  return (
    <li className="day-row check faderin">
      <div className="income-outcome">
        <div className="vl-changable"></div>
        <ul className="global income dayIncome">
          {context.rows[props.id]?.map((el) => (
            <Item key={el.id} rowId={props.id} id={el.id} />
          ))}
          <FontAwesomeIcon
            className="changable margin-left"
            onClick={() => context.addItem(props.id)}
            icon={["fas", "plus-circle"]}
            size="lg"
          />
        </ul>
      </div>
    </li>
  );
}

Removing a single element

Removing a single element from state was covered above, but you need to pass the correct row and element ids to the removeItem handler.

function ChangeablelItem(props) {
  const { rowId, id } = props;
  const context = useContext(appContext);
  ...
  return (
    <li className="day-item">
      ... editable div ...
      <div className="icons">
        {iconsVisble && (
          <RemoveIcon
            removeItem={() => context.removeItem(rowId, id)}
          />
        )}
      </div>
    </li>
  );
}

Hiding an icon after click outside of the item

This was actually the easiest issue to resolve. Use onFocus and onBlur handlers to set the iconVisible state to show the delete button. I used a setTimeout on the blur handler to keep it the delete button mounted long enough when clicked to call the removeItem handler.

// Icon State
const [iconsVisble, setIconVisble] = useState(false);

...

<div
  className="number amount"
  onFocus={() => setIconVisble(true)}
  onBlur={() => setTimeout(() => setIconVisble(false), 200)}
  contentEditable="true"
/>

Edit react-context-api-context-api-issues-when-adding-and-removing-an-items-from-ui