DEV Community

Cover image for React Hooks and Local Storage: Let’s build a ToDo app

React Hooks and Local Storage: Let’s build a ToDo app

saransh kataria on August 26, 2020

Originally published at https://www.wisdomgeek.com on August 25, 2020. React hooks have been around for quite some time. They have been widely ado...
Collapse
 
bethwickerson profile image
bethwickerson

This post has been really helpful to me. I am building a class selection app and I was able to modify the form to pull from a list array. I am struggling to figure out how to toggle a boolean value in the reducer and localStorage, though. I need to be able to detect whether the item was selected throughout various components in the app so that I can prevent it from being selected twice.

Do you have any tips on how to prevent duplicates?

Collapse
 
bethwickerson profile image
bethwickerson

items.js

const itemsReducer = (state, action) => {
switch (action.type) {
case 'POPULATE_ITEMS':
return action.items;
case 'ADD_ITEM':
return state.concat({ name: action.name, info: action.info });
case 'REMOVE_ITEM':
return state.filter((item) => item !== action.itemToBeDeleted);
default:
return state;
}
};
export { itemsReducer as default };

Collapse
 
bethwickerson profile image
bethwickerson

AddItemForm.js

(Each item in my "individualClassList" array has a boolean isSelected: false)

import React, { useContext, useState } from 'react';
import ItemsContext from '../../components/context/items-context'
import individualClasses from '../../components/ClassLists/individualClasses'
import { v4 as uuidv4 } from 'uuid'

const ItemIndividual = ({ item, setShow, setCurrent }) => {

  const { itemsDispatch } = useContext(ItemsContext);
  const [individualClassList, setList_Individual] = useState(individualClasses);

  const handleAdd = (name, info) => {
    itemsDispatch({ type: 'ADD_ITEM', name, info });
    setShow(true);
    setCurrent(name);
    //toggle button disable _Individual class list, this works
    const newListAdd_Individual = individualClassList.map((item) => {
       if ((item.name) === name) {
         const updatedItemAdd_Individual = {
           ...item,
           isSelected: !item.isSelected,
         };
         return updatedItemAdd_Individual;
       }
       return item;
     });
     setList_Individual(newListAdd_Individual);
  };

  const handleRemove = (e) => {
    itemsDispatch({ type: 'REMOVE_ITEM', itemToBeDeleted: item })
    setCurrent(null);
    // toggle button disable _Individual class list, this does not work
     const newListAdd_Individual = individualClassList.map((item) => {
      if ((item.name) === e) {
         const updatedItemAdd_Individual = {
           ...item,
           isSelected: !item.isSelected,
         };
         return updatedItemAdd_Individual;
       }
       return item;
     });
     setList_Individual(newListAdd_Individual);
  };

  return (
    <div>
      <ul>
        {individualClassList.map(item => (
          <li key={uuidv4()} className="mb-5">
            <h6 className="has-letter-spacing">{item.name}</h6>
            {item.isSelected ?
              <button
                value={item.name}
                className="button link normal is-italic"
                onClick={(e) => handleRemove(e.target.value)}
              >SELECTED</button> :
              <button
                value={item.name}
                className="button link"
                onClick={() => handleAdd(item.name, item.info)}
              >
                SELECT
              </button>
            }

          </li>

        ))}
      </ul>
    </div>
  );
};

export { ItemIndividual as default };
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
bethwickerson profile image
bethwickerson

Everything else is essentially the same as your example. I tried adding the isSelected boolean to the itemReducer "ADD_ITEM" case, but I couldn't figure out how to add it to the "REMOVE_ITEM" case. I've been banging my head on this on for many days, so if you have any clues I would really appreciate the help!

Thread Thread
 
saranshk profile image
saransh kataria

I think you are mixing two concepts: useState and useReducer. If you are using the reducer to update the state, you do not need to use useState again. I am not sure why you are doing that in the handleAdd and handleRemove methods. I would recommend reading more about useReducer to grasp it's concepts first. This post about useReducer should help you in doing that.

And for the remove not working, you are updating the value in the array with isSelected to false, not removing it. If you wanted to remove the entry on remove, you should use

const newListRemove_Intro = introClassList.filter((item) => {
      return (item.name !== name)
});
    setList_Intro(newListRemove_Intro);
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
bethwickerson profile image
bethwickerson

Thank you so much for you response. It is really confusing because I am updating the state of two different arrays. One is my original list with items to select, which are added to a new list with the useReducer. In the original list I was adding useState to be able to detect whether an item in my original list has been selected so I can toggle the handleAdd/handleRemove function, and prevent duplicates from being added to my new list, but they are out of sync with each other.
Is there a way to prevent duplicates in the useReducer function? I am trying to concat and filter, but nothing is working and I have been reading about reducers and context until my eyes bug out. I feel like the answer could be as simple as concatenating the selected items and then filtering out the duplicates, but I only get errors or empty arrays when I do anything to this line.

case 'ADD_ITEM': return state.concat({ name: action.name, info: action.info });

Thread Thread
 
saranshk profile image
saransh kataria

you can change it to:

case 'ADD_ITEM':
if(state.find(obj => obj.name === action.name)){
return state;
}
else {
return state.concat({ name: action.name, info: action.info });
}

That should work. Also, I had to download the code at github.com/bethwickerson to understand the challenge you were facing and a suggestion: you don't need the .cache folder and node_modules committed to git. You can add those to .gitignore and then also run git rm -r --cached node_modules to remove it from git cache. Hope that helps!

Thread Thread
 
bethwickerson profile image
bethwickerson

That did it! I cannot possibly thank you enough. I would have never thought to code that myself like that, and kudos to you for finding my repo, as you can see it is for a good organization. I learned a ton from your posts, I really appreciate you taking the time to solve this issue for me. And thank you for the suggestion on the .gitignore, I had thought I added node_modules but I forgot the ending /

This has helped so much!

Thread Thread
 
saranshk profile image
saransh kataria

You are welcome. Glad I could help!

Collapse
 
schinta2 profile image
siva sandeep chintamaneni

"As opposed to state in class components, useState does not work with object values." - Is this accurate?
Wondering if I misunderstood that statement as I believe, the following post has a working example of using object value instead
dev.to/reedbarger/react-hooks-chea...

Collapse
 
saranshk profile image
saransh kataria

Thanks for pointing that out. The verbiage is incorrect. It does work with object values, but primitives can be used as well and it is the preferred way of using it unless the values in the object are related. I will update the post to reflect the same.

Collapse
 
sukarnascience profile image
Sukarna Jana

You are great ... From few days I was scratching my head to solve this issue and you have solve in just a second ❤️😌😍😍

Collapse
 
saranshk profile image
saransh kataria

Happy to hear that it helped!