Professional-React-and-Next.js-Course
Professional-React-and-Next.js-Course copied to clipboard
A few improvements in TrekBag project
- 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.
- 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>
);
}