Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active October 28, 2025 20:49
Show Gist options
  • Save sunmeat/fecd4f307daf2178a939d6236e4805cc to your computer and use it in GitHub Desktop.
Save sunmeat/fecd4f307daf2178a939d6236e4805cc to your computer and use it in GitHub Desktop.
IntersectionObserver
import { useState, useEffect, useRef } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import './App.css';
const API_URL = 'http://sunmeat.atwebpages.com/react/api.php';
// екземпляр QueryClient
const queryClient = new QueryClient();
const truncateText = (text, maxLength) =>
text.length > maxLength ? text.slice(0, maxLength) + '...' : text;
// компонента для отримання та відображення продуктів
function ProductList({ addToCart }) {
const [page, setPage] = useState(1);
const [allProducts, setAllProducts] = useState([]);
const loadMoreRef = useRef(null);
// API для отримання продуктів
const fetchProducts = async ({ queryKey }) => {
const [, page] = queryKey;
const response = await fetch(`https://fakestoreapi.com/products?limit=12&offset=${(page - 1) * 12}`);
if (!response.ok) throw new Error('помилка завантаження продуктів');
return response.json();
};
// useQuery для управління даними
const { data, error, isLoading } = useQuery({
queryKey: ['products', page],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // кешування на 5 хвилин
});
// оновлення списку продуктів при отриманні нових даних
useEffect(() => {
if (data) {
setAllProducts((prev) => [...prev, ...data]);
}
}, [data]);
// налаштування IntersectionObserver для нескінченної прокрутки
useEffect(() => {
// IntersectionObserver відстежує видимість елемента
// коли елемент стає видимим на 10% (threshold: 0.1) і дані не завантажуються (!isLoading),
// збільшує номер сторінки (setPage), ініціюючи підвантаження нових даних для нескінченної прокрутки
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && !isLoading) {
setPage((prev) => prev + 1);
}
},
{ threshold: 0.1 }
);
if (loadMoreRef.current) {
observer.observe(loadMoreRef.current);
}
return () => {
if (loadMoreRef.current) {
observer.unobserve(loadMoreRef.current);
}
};
}, [isLoading]);
if (error) return <div className="error">помилка: {error.message}</div>;
return (
<div className="product-list">
<h2>наші товари:</h2>
<div className="products">
{allProducts.map((product) => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.title} className="product-image" />
<h3>{truncateText(product.title, 30)}</h3>
<p className="price">${product.price}</p>
<button onClick={() => addToCart(product)} className="add-to-cart">
додати до кошика
</button>
</div>
))}
</div>
{isLoading && <div className="loading">завантаження...</div>}
<div ref={loadMoreRef} style={{ height: '20px' }} />
</div>
);
}
// компонента кошика
function Cart({ cart, removeFromCart }) {
const total = cart.reduce((sum, item) => sum + Number(item.price), 0).toFixed(2);
return (
<div className="cart">
<h2>кошик</h2>
{cart.length === 0 ? (
<p>кошик порожній</p>
) : (
<div>
{cart.map((item) => (
<div key={item.id} className="cart-item">
<span>{truncateText(item.title, 30)}</span>
<span>${item.price}</span>
<button onClick={() => removeFromCart(item.id)} className="remove-from-cart">
видалити
</button>
</div>
))}
<p className="total">разом: ${total}</p>
</div>
)}
</div>
);
}
function App() {
const [cart, setCart] = useState([]);
const [error, setError] = useState(null);
// завантаження кошика з API при ініціалізації
useEffect(() => {
const loadCart = async () => {
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('помилка завантаження кошика');
const data = await response.json();
if (data.error) throw new Error(data.error);
setCart(data);
} catch (err) {
setError(err.message);
}
};
loadCart();
}, []);
// додавання товару до кошика через API
const addToCart = async (product) => {
try {
const newItem = {
id: product.id,
title: product.title,
price: Number(product.price),
image: product.image,
};
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newItem),
});
if (!response.ok) throw new Error('помилка додавання до кошика');
const result = await response.json();
if (result.error) throw new Error(result.error);
setCart((prevCart) => [
...prevCart,
{
...newItem,
product_id: newItem.id,
id: result.id,
},
]);
} catch (err) {
setError(err.message);
}
};
// видалення товару з кошика через API
const removeFromCart = async (cartItemId) => {
try {
const response = await fetch(API_URL, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: cartItemId }),
});
if (!response.ok) throw new Error('помилка видалення з кошика');
const result = await response.json();
if (result.error) throw new Error(result.error);
setCart((prevCart) => prevCart.filter((item) => item.id !== cartItemId));
} catch (err) {
setError(err.message);
}
};
return (
<QueryClientProvider client={queryClient}>
<div className="app">
<h1>інтернет-магазин ReactExpress</h1>
{error && <div className="error">помилка: {error}</div>}
<ProductList addToCart={addToCart} />
<Cart cart={cart} removeFromCart={removeFromCart} />
</div>
</QueryClientProvider>
);
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment