Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A few improvements in TrekBag project #16

Open
Rope-a-dope opened this issue Jun 12, 2024 · 0 comments
Open

A few improvements in TrekBag project #16

Rope-a-dope opened this issue Jun 12, 2024 · 0 comments

Comments

@Rope-a-dope
Copy link

Rope-a-dope commented Jun 12, 2024

  1. Since we use Zustand to manage the state, there is no need to pass props to AddItemForm and Counter, we can just use Zustand in them directly.
  2. It's always a good idea to use custom hooks as explained in https://tkdodo.eu/blog/working-with-zustand and your video https://www.youtube.com/watch?v=I7dwJxGuGYQ&t=69s&ab_channel=ByteGrad. It's the same for other tools as for useContext. But of course, we must only persist the state here, otherwise we will have an error like "addItem is not a function"

itemsStore.js

import { create } from "zustand";
import { persist } from "zustand/middleware";
import { initialItems } from "../lib/constants";

export const useItemsStore = create(
  persist(
    (set) => ({
      items: initialItems,
      // Separate Actions from State
      actions: {
        addItem: (newItemText) => {
          const newItem = {
            id: new Date().getTime(),
            name: newItemText,
            packed: false,
          };

          set((state) => ({ items: [...state.items, newItem] }));
        },
        deleteItem: (id) => {
          set((state) => {
            const newItems = state.items.filter((item) => item.id !== id);
            return { items: newItems };
          });
        },
        toggleItem: (id) => {
          set((state) => {
            const newItems = state.items.map((item) => {
              if (item.id === id) {
                return { ...item, packed: !item.packed };
              }

              return item;
            });
            return { items: newItems };
          });
        },
        removeAllItems: () => {
          set(() => ({ items: [] }));
        },
        resetToInitial: () => {
          set(() => ({ items: initialItems }));
        },
        markAllAsComplete: () => {
          set((state) => {
            const newItems = state.items.map((item) => {
              return { ...item, packed: true };
            });

            return { items: newItems };
          });
        },
        markAllAsIncomplete: () => {
          set((state) => {
            const newItems = state.items.map((item) => {
              return { ...item, packed: false };
            });

            return { items: newItems };
          });
        },
      },
    }),
    {
      name: "items",
      partialize: ({ state }) => ({ state }), // only persist state
    }
  )
);

// exported - consumers don't need to write selectors. Prefer atomic selectors
export const useItems = () => useItemsStore((state) => state.items); 
// 🎉 one selector for all our actions.  As actions never change, it doesn't matter that we subscribe to "all of them". The actions object can be seen as a single atomic piece.
export const useItemsActions = () => useItemsStore((state) => state.actions);

AddItemForm.jsx

import { useRef, useState } from "react";
import { useItemsActions } from "../stores/itemsStore";
import Button from "./Button";

export default function AddItemForm() {
  const [itemText, setItemText] = useState("");
  const inputRef = useRef();

  const { addItem } = useItemsActions();
  const handleSubmit = (e) => {
    e.preventDefault();

    // basic validation
    if (!itemText) {
      alert("Item can't be empty");
      inputRef.current.focus();
      return;
    }

    addItem(itemText);
    setItemText("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Add an item</h2>
      <input
        ref={inputRef}
        value={itemText}
        onChange={(e) => {
          setItemText(e.target.value);
        }}
        autoFocus
      />
      <Button>Add to list</Button>
    </form>
  );
}

Counter.jsx

import { useItems } from "../stores/itemsStore";

export default function Counter() {
  const items = useItems();
  const numberOfItemsPacked = items.filter((item) => item.packed).length;
  const totalNumberOfItems = items.length;

  return (
    <p>
      <b>{numberOfItemsPacked}</b> / {totalNumberOfItems} items packed
    </p>
  );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant