Les hooks par la pratique : mettre en place un sytème de favoris

Créez vos premiers hooks et ré-utilisez les dans vos applications React

English version : Hooks in practice : system of favorites in localstorage

Objectifs

  • Découvrir comment ajouter des hooks à nos composants React
  • Ecrire de la logique ré-utilisable facilement
  • Utiliser l'api localstorage

Vous pouvez tester l'application et voir les sources de ce tutorial.

Contexte

Les hooks ont été introduits par les développeurs de React pour permettre une meilleure organisation du code en exportant certains éléments de logique des composants. Plusieurs hooks sont développés et utilisés au sein d'un projet, et la simplicité avec laquelle ils peuvent être ré-utilisés en font un outil incontournable de l'écosystème React.

Un peu de théorie

Nous allons mettre en place pas à pas un hook permettant de gérer un système de favoris. Notre application gère des utilisateurs, et nous souhaitons pouvoir mettre en favori certains utilisateurs, les supprimer de nos favoris et les retrouver facilement.

Pour cela nous allons utiliser le localstorage. Chaque utilisateur possède un ID unique, nos favoris seront stockés sous forme de tableau contenant des IDs, enregistré sous forme de chaîne de caractères dans le localstorage.

Lancement du projet

Nous utiliserons l'outil create-react-app pour ne pas se soucier de la configuration du projet et se concentrer sur le code.

Node.js et npm doivent être installés, puis la commande suivante exécutée pour créer le projet :

npx create-react-app bookmark-hook-tutorial

Création de nos composants

Un premier composant permettra d'afficher un utilisateur :

// User.js
{usersState.map((user) => {
  return (
    <div key={user.id} className={`user`}>
      <div>{user.username}</div>
    </div>
  );
})}

Les utilisateurs seront ensuite listés par un autre composant :

// Users.js
function Users({ users }) {
  return (
    <div className={"root"}>
      <div className={"users"}>
        {usersState.map((user) => {
          return (
            <div key={user.id} className={`user`}>
              <div>{user.username}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Enfin la liste des utilisateurs est rendue dans notre application, en récupérant une liste locale d'utilisateurs :

// App.js
function App() {
  return <Users users={users} />;
}

Mise en place du hook

Dans notre exemple nous souhaitons gérer des favoris. On veut pouvoir ajouter, supprimer un favori, consulter la liste des favoris, et savoir si un utilisateur est en favori ou non.

// useBookMarks.js
function useBookmarks() {
  const [bookmarks, setBookmarks] = useState(() => {
    const ls = localStorage.getItem("bookmarks");
    if (ls) return JSON.parse(ls);
    else return [];
  });

  const toggleItemInLocalStorage = (id) => () => {
    const isBookmarked = bookmarks.includes(id);
    if (isBookmarked) setBookmarks((prev) => prev.filter((b) => b !== id));
    else setBookmarks((prev) => [...prev, id]);
  };

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

  return [bookmarks, toggleItemInLocalStorage];
}

Utilisation de notre hook dans notre composant

Il ne nous reste plus qu'à utiliser la logique de nos hooks au sein de nos composants. Pour le composant User, on ajoute la fonctionnalité d'ajout / suppression en favoris :

// User.js
{usersState.map((user) => {
  const isBookmarked = bookmarks.includes(user.id);
  return (
    <div key={user.id} className={`user ${isBookmarked ? "bookmarked" : ""}`}>
      <div>{user.username}</div>
      <button onClick={toggleBookmark(user.id)}>
        {isBookmarked ? "Remove from bookmarks" : "Add to bookmarks"}
      </button>
    </div>
  );
})}

Pour la liste des utilisateurs, on ajoute une fonctionnalité de filtre, et un peu de style :

// Users.js
function Users({ users }) {
  const [bookmarksOnly, setBookmarksOnly] = useState(false);
  const [usersState, setUsersState] = useState(users);
  const [bookmarks, toggleBookmark] = useBookmarks();

  const changeBookMarksOnly = (e) => {
    setBookmarksOnly(e.target.checked);
  };

  useEffect(() => {
    setUsersState(users.filter((s) => (bookmarksOnly ? bookmarks.includes(s.id) : s)));
  }, [users, bookmarksOnly, bookmarks]);

  return (
    <div className={"root"}>
      <label htmlFor="check">Bookmarked users only</label>
      <input id={"check"} type="checkbox" value={bookmarksOnly} onChange={changeBookMarksOnly} />
      <div className={"users"}>
        {usersState.map((user) => {
          const isBookmarked = bookmarks.includes(user.id);
          return (
            <div key={user.id} className={`user ${isBookmarked ? "bookmarked" : ""}`}>
              <div>{user.username}</div>
              <button onClick={toggleBookmark(user.id)}>
                {isBookmarked ? "Remove from bookmarks" : "Add to bookmarks"}
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Notre système de favoris est opérationnel, et il persiste même après actualisation ou fermeture de l'application grâce au localstorage.

Nos composants sont peu impactés par la logique de sauvegarde en localstorage, et notre hook pourra facilement être ré-utilisé dans une autre application.

Vous pouvez retrouver toutes les sources ici et une démo de l'application