Created
June 14, 2025 14:10
-
-
Save sunmeat/66fcd2582e568bff9b720a2ee7d25a66 to your computer and use it in GitHub Desktop.
zustand example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useEffect } from 'react'; | |
import { create } from 'zustand'; // npm install zustand | |
import './App.css'; | |
// создание zustand store для управления корзиной и товарами | |
const useStore = create((set, get) => ({ | |
products: [], // список доступных товаров | |
cartItems: [], // товары в корзине | |
status: 'idle', // статус загрузки (idle, loading, succeeded, failed) | |
error: null, // сообщение об ошибке | |
// асинхронный поиск товаров через api | |
fetchProducts: async (searchQuery = '') => { | |
set({ status: 'loading', error: null }); // установка статуса загрузки и очистка ошибок | |
try { | |
const response = await fetch('https://fakestoreapi.com/products'); | |
if (!response.ok) throw new Error('ошибка загрузки товаров'); | |
const products = await response.json(); | |
// фильтрация на стороне клиента для поиска | |
const filteredProducts = products.filter(product => | |
product.title.toLowerCase().includes(searchQuery.toLowerCase()) | |
); | |
set({ products: filteredProducts, status: 'succeeded' }); // сохранение товаров и статуса | |
} catch (error) { | |
set({ error: error.message, status: 'failed' }); // установка ошибки и статуса | |
} | |
}, | |
// добавление товара в корзину | |
addToCart: ({ id, title, price }) => { | |
set(state => { | |
const cartItem = state.cartItems.find(item => item.id === id); | |
const product = state.products.find(p => p.id === id); | |
if (product) { | |
if (cartItem) { | |
return { | |
cartItems: state.cartItems.map(item => | |
item.id === id ? { ...item, quantity: item.quantity + 1 } : item | |
) | |
}; | |
} else { | |
return { | |
cartItems: [...state.cartItems, { id, title, price, quantity: 1 }] | |
}; | |
} | |
} | |
return state; | |
}); | |
}, | |
// удаление товара из корзины | |
removeFromCart: (id) => { | |
set(state => ({ | |
cartItems: state.cartItems.filter(item => item.id !== id) | |
})); | |
}, | |
// изменение количества товара | |
updateQuantity: (id, quantity) => { | |
set(state => { | |
const cartItem = state.cartItems.find(item => item.id === id); | |
if (cartItem && quantity > 0) { | |
return { | |
cartItems: state.cartItems.map(item => | |
item.id === id ? { ...item, quantity } : item | |
) | |
}; | |
} | |
return state; | |
}); | |
}, | |
// очистка корзины | |
clearCart: () => { | |
set({ cartItems: [] }); | |
}, | |
// очистка ошибки | |
clearError: () => { | |
set({ error: null }); | |
}, | |
})); | |
// компонент поиска и списка товаров | |
function ProductList() { | |
const { products, cartItems, status, error, fetchProducts } = useStore(); // хук для получения состояния и методов | |
const addToCart = useStore(state => state.addToCart); // выбор метода для оптимизации рендера | |
const [searchQuery, setSearchQuery] = useState(''); // локальное состояние для поиска | |
useEffect(() => { | |
fetchProducts(searchQuery); // загрузка товаров при изменении запроса | |
}, [fetchProducts, searchQuery]); | |
const handleSearch = (e) => { | |
setSearchQuery(e.target.value); // обновление поискового запроса | |
}; | |
const getAvailableStock = (productId) => { | |
const cartItem = cartItems.find(item => item.id === productId); | |
return cartItem ? 10 - cartItem.quantity : 10; // предполагаем максимум 10 единиц на товар | |
}; | |
return ( | |
<div className="product-list"> | |
<h2 className="section-title">Поиск товаров</h2> | |
<input | |
className="search-input" | |
type="text" | |
value={searchQuery} | |
onChange={handleSearch} | |
placeholder="Введите название товара..." | |
/> | |
{status === 'loading' && <p className="loading">Загрузка...</p>} | |
{error && <p className="error">{error}</p>} | |
{status === 'succeeded' && products.length === 0 && <p className="empty">Товары не найдены</p>} | |
<ul className="product-grid"> | |
{products.map(product => ( | |
<li key={product.id} className="product-item"> | |
<span className="product-name">{product.title}</span> | |
<span className="product-price">{product.price} $</span> | |
<span className="product-stock">Остаток: {getAvailableStock(product.id)}</span> | |
<button | |
className="cart-button" | |
onClick={() => addToCart({ | |
id: product.id, | |
title: product.title, | |
price: product.price | |
})} | |
disabled={getAvailableStock(product.id) === 0} | |
> | |
Добавить в корзину | |
</button> | |
</li> | |
))} | |
</ul> | |
</div> | |
); | |
} | |
// компонент корзины | |
function Cart() { | |
const { cartItems, updateQuantity, removeFromCart, clearCart } = useStore(); // хук для получения состояния и методов | |
const total = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0); | |
const getMaxQuantity = () => { | |
return 10; // предполагаем максимум 10 единиц на товар | |
}; | |
return ( | |
<div className="cart"> | |
<h2 className="section-title">Корзина</h2> | |
{cartItems.length === 0 ? ( | |
<p className="empty">Корзина пуста</p> | |
) : ( | |
<> | |
<ul className="cart-list"> | |
{cartItems.map(item => ( | |
<li key={item.id} className="cart-item"> | |
<span className="cart-item-name">{item.title}</span> | |
<span className="cart-item-price">{item.price} $ x {item.quantity}</span> | |
<div className="cart-item-controls"> | |
<input | |
type="number" | |
className="cart-item-quantity" | |
value={item.quantity} | |
min="1" | |
max={getMaxQuantity(item.id)} | |
onChange={(e) => updateQuantity(item.id, parseInt(e.target.value) || 1)} | |
/> | |
<button | |
className="cart-button remove" | |
onClick={() => removeFromCart(item.id)} | |
> | |
Удалить | |
</button> | |
</div> | |
</li> | |
))} | |
</ul> | |
<div className="cart-total"> | |
<span>Итого: {total.toFixed(2)} $</span> | |
<button | |
className="cart-button clear" | |
onClick={clearCart} | |
> | |
Очистить корзину | |
</button> | |
</div> | |
</> | |
)} | |
</div> | |
); | |
} | |
// компонент магазина | |
function ShopApp() { | |
return ( | |
<div className="shop-app"> | |
<h1 className="app-title">ReactExpress</h1> | |
<ProductList /> | |
<Cart /> | |
</div> | |
); | |
} | |
// корневой компонент | |
function App() { | |
return <ShopApp />; | |
} | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment