How to remove bookmarked element from local storage

Viewed 51

I am making an app for bookmarking characters. I have to store Bookmarked characters in local Storage so when I refresh the page I can see my bookmarked elements.

My elements are saving correctly into local storage but when I refresh the page the state of bookmarked icon is not saved (It is put back on unfilled icon although elements are still in local storage)

The second problem is that I don't know how to implement removing bookmarked elements from local storage. I want to un-bookmark specific element and remove that element from local storage. You can see in my handleBookmarking function how I did bookmarking but I don't know how to do two-way function for bookmarking and -unbookmarking. Any ideas maybe?

Here is the code:

CharactersList.js

import React, { useState } from "react";
import "./CharactersList.css";
import { RiBookmarkLine } from "react-icons/ri";
import { RiBookmarkFill } from "react-icons/ri";

const CharactersList = (props) => {
  const [isLiked, setIsLiked] = useState([]);
  const [favourites, setFavourites] = useState([]);

  const saveToLocalStorage = (data) => {
    localStorage.setItem("react-bookmarked-characters", JSON.stringify(data));
  };

  const handleBookmarking = (character) => {
    if (isLiked.includes(character.id)) {
      const newIsLiked = [...isLiked];
      setIsLiked(newIsLiked);
    } else {
      setIsLiked([...isLiked, character.id]);
      const newBookmarkedList = [...favourites, character];
      setFavourites(newBookmarkedList);
      saveToLocalStorage(newBookmarkedList);
      
    }
  };

  const Bookmarked = ({ id }) => {
    return isLiked.includes(id) ? <RiBookmarkFill /> : <RiBookmarkLine />;
  };
  
  return (
    <>
      {props.characters.map((character, index) => (
        <div className="card-container" key={index}>
          <img
            src={`${character.thumbnail.path}/standard_fantastic.${character.thumbnail.extension}`}
            alt="character"
            className="image"
          />
          <div className="info-container">
            <p className="name">{character.name}</p>
            <div
              className="icon"
              onClick={() => {
                handleBookmarking(character);
              }}
            >
              <Bookmarked id={character.id} />
            </div>
          </div>
        </div>
      ))}
    </>
  );
};

export default CharactersList;

App.js So in the app.js I am calling two lists of characters, one is when the search input is empty (shows all bookmarked elements) and the second one is on search (shows all elements).

const App = () => {
  const [characters, setCharacters] = useState([]);
  const [search, setSearch] = useState("");
  const [favourites, setFavourites] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [characterPerPage] = useState(20);

  const getCharactersRequest = async () => {
    const url = `https://gateway.marvel.com/v1/public/characters?nameStartsWith=${search}&ts=1&apikey=&hash=`; 
    const response = await fetch(url);
    const responseJSON = await response.json();

    if (responseJSON.data) {
      setCharacters(responseJSON.data.results);
    }
  };

  useEffect(() => {
    getCharactersRequest(search);
  }, [search]);

  useEffect(() => {
    const bookMarkedData = JSON.parse(
      localStorage.getItem("react-bookmarked-characters")
    );
    if (bookMarkedData) {
      setFavourites(bookMarkedData);
    }
  }, [search]);

  // Get 20 posts
  const indexOfLastCharacter = currentPage * characterPerPage;
  const indexOfFirstCharacter = indexOfLastCharacter - characterPerPage;
  const slicedFavourites = favourites.slice(
    indexOfFirstCharacter,
    indexOfLastCharacter
  );

  return (
    <div>
      <div className="heading-container">
        <SearchBarHeading heading={"MARVEL CHARACTERS"} />
        <SearchBar search={search} setSearch={setSearch} />
      </div>

      <div className="cards-container">
        {search === "" ? (
          <CharactersList characters={slicedFavourites} />
        ) : (
          <CharactersList characters={characters} />
        )}
      </div>
    </div>
  );
};

export default App;
2 Answers

You can use this function.

to add, isAdd should be true. flase otherwise.

const handleBookmark = (character, isAdd) => {
  data = JSON.parse(localStorage.getItem("react-bookmarked-characters"));
  if(isAdd){
    //add to bookmark
    data = [...data, character];
  }else{
    //remove from bookmark
    data = data.filter(item => item.id !== character.id);
  }
  localStorage.setItem("react-bookmarked-characters", JSON.Stringify(data));
}

Here is how I would solve it.

First thing to do, is to read the saved bookmarks and set them as the initial state of the component.

Then, I would use React.useEffect to persist the bookmarks state, any time that state changes.

Finally, I would cleanup your onClick handler a little bit, to handle the toggling of the bookmarked ids.

import React, { useState } from 'react';
import './CharactersList.css';
import { RiBookmarkLine } from 'react-icons/ri';
import { RiBookmarkFill } from 'react-icons/ri';

const StorageKey = 'react-bookmarked-characters';

const CharactersList = (props) => {
  const initialState = JSON.parse(window.localStorage.getItem(StorageKey)) ?? [];
  const [bookmarks, setBookmarks] = useState(initialState);

  React.useEffect(() => {
    window.localStorage.setItem(StorageKey, JSON.stringify(bookmarks));
  }, [bookmarks]);

  const handleLikeChange = (character) => {
    setBookmarks((prevBookmarks) => {
      if (prevBookmarks.includes.character.id) {
        return prevBookmarks.filter((id) => id !== character.id);
      }
      return [...prevBookmarks, character.id];
    });
  };

  return props.characters.map((character) => (
    <Character
      key={character.index}
      character={character}
      bookmarked={bookmarks.includes(character.id)}
      onToggleBookmark={handleLikeChange}
    />
  ));
};

const Character = ({ character, bookmarked, onToggleBookmark }) => {
  const handleClick = () => onToggleBookmark(character);

  return (
    <div className="card-container" key={character.index}>
      <img
        src={`${character.thumbnail.path}/standard_fantastic.${character.thumbnail.extension}`}
        alt="character"
        className="image"
      />
      <div className="info-container">
        <p className="name">{character.name}</p>
        <div className="icon" onClick={handleClick}>
          {bookmarked ? <RiBookmarkFill /> : <RiBookmarkLine />}
        </div>
      </div>
    </div>
  );
};

export default CharactersList;
Related