feat: update project
This commit is contained in:
@@ -1,3 +1,46 @@
|
||||
|
||||
## Map
|
||||
|
||||
Ein **`Array`** ist eine geordnete Liste indizierter Werte (Zugriff über numerische Indizes `0, 1, 2...`), während eine **`Map`** eine geordnete Sammlung von **Schlüssel-Wert-Paaren (Key-Value Pairs)** ist, bei der beliebige Datentypen als Schlüssel dienen können.
|
||||
|
||||
| Merkmal | `Array` (`[]` / `new Array()`) | `Map` (`new Map()`) |
|
||||
| --- | --- | --- |
|
||||
| **Datenstruktur** | Geordnete Liste von Elementen | Geordnete Schlüssel-Wert-Paare |
|
||||
| **Schlüssel / Indizes** | Ausschließlich sequentielle Integer (`0, 1, 2...`) | Beliebige Typen (Objekte, Funktionen, Strings, Booleans) |
|
||||
| **Elementanzahl** | `array.length` | `map.size` |
|
||||
| **Zugriff / Abfrage** | Index `arr[0]` oder Suche `arr.find(...)` ($O(n)$) | Direkter Lookup `map.get(key)` ($O(1)$) |
|
||||
| **Vorhandensein prüfen** | `arr.includes(val)` ($O(n)$) | `map.has(key)` ($O(1)$) |
|
||||
| **Einfügen / Löschen** | `push()`, `splice()` (Verschiebung im Speicher) | `set(key, val)`, `delete(key)` (Optimiert für Keys) |
|
||||
|
||||
---
|
||||
|
||||
**Wann macht `Map` Sinn?**
|
||||
|
||||
* **Häufiges Nachschlagen (Lookups):** Wenn du Werte anhand eines eindeutigen Identifikators (ID, Slug, Token) extrem schnell abrufen musst ($O(1)$ statt Array-Iteration mit `find()`).
|
||||
* **Objekte oder Funktionen als Schlüssel:** Im Gegensatz zu regulären JS-Objekten (die Keys immer in Strings/Symbols umwandeln) akzeptiert `Map` Referenzen als Key:
|
||||
```javascript
|
||||
const userMap = new Map();
|
||||
const userObj = { id: 42 };
|
||||
|
||||
userMap.set(userObj, { role: "admin", active: true });
|
||||
console.log(userMap.get(userObj)); // { role: "admin", active: true }
|
||||
|
||||
```
|
||||
|
||||
|
||||
* **Häufiges Hinzufügen und Entfernen:** `Map` ist intern für dynamisches Hinzufügen (`set`) und Löschen (`delete`) optimiert. Beim Entfernen aus einem Array via `splice()` müssen alle nachfolgenden Indizes neu berechnet werden.
|
||||
* **Saubere Schlüssel ohne Prototype-Pollution:** Ein normales Objekt `{}` erbt Standard-Methoden wie `toString`. Eine `Map` enthält strikt nur die Schlüssel, die du selbst definierst.
|
||||
|
||||
---
|
||||
|
||||
**Wann bleibt `Array` die bessere Wahl?**
|
||||
|
||||
* Wenn die **Reihenfolge und Indexposition** im Vordergrund stehen (z. B. Sortieren, Paginieren).
|
||||
* Wenn du Transformations-Pipelines wie `.map()`, `.filter()`, `.reduce()` oder `.flat()` brauchst.
|
||||
* Für homogene Datenlisten, die direkt als JSON serialisiert werden sollen (`JSON.stringify()` unterstützt Arrays nativ, Maps müssen vorher konvertiert werden).
|
||||
|
||||
---
|
||||
|
||||
# React + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import RowProduct from './content/RowProduct';
|
||||
import { ProductsProvider, useProductsState } from '../../stores/products';
|
||||
import { ProductsProvider, useProductsActions, useProductsState } from '../../stores/products';
|
||||
import { FaSpinner } from 'react-icons/fa6';
|
||||
|
||||
// import { useContext } from 'react';
|
||||
// import { ProductsStateContext } from '../../stores/products/ProductsStore';
|
||||
|
||||
const TableProductsView = (props) => {
|
||||
const { items } = useProductsState(); // const {items} = useContext(ProductsStateContext);
|
||||
// const state = useContext(ProductsStateContext);
|
||||
// const { items, loading, error } = state;
|
||||
const { items, loading, error } = useProductsState(); // const {items} = useContext(ProductsStateContext);
|
||||
|
||||
const { loadProducts } = useProductsActions();
|
||||
|
||||
// const dispatch = useProductsDispatch() // const dispatch = useContext(ProductsDispatchContext);
|
||||
|
||||
//const [{ items }, dispatch] = useReducer(reducer, initialState(props));
|
||||
|
||||
// EventHandler ============
|
||||
@@ -26,16 +33,28 @@ const TableProductsView = (props) => {
|
||||
}, 0);
|
||||
}, [items]);
|
||||
|
||||
// useCallback weiter Einsatzbereich in Kombination mit ref- Attribut
|
||||
const theadRef = useCallback((el) => {
|
||||
if (el) console.log(el);
|
||||
}, []);
|
||||
if (loading && items.length === 0) {
|
||||
return (
|
||||
<p className="text-muted alert alert-info">
|
||||
Produkte werden geladen....
|
||||
<FaSpinner className="fa-spin" />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-table-products table-products">
|
||||
<div className="container py-2">
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<p className="mb-2">{error}</p>
|
||||
<button type="button" className="btn btn-sm btn-dark" onClick={loadProducts}>
|
||||
Erneut laden
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<table className="table table-striped">
|
||||
<thead className="table-dark" ref={theadRef}>
|
||||
<thead className="table-dark">
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>Stock</th>
|
||||
|
||||
@@ -1,34 +1,281 @@
|
||||
import { createContext, useCallback, useContext, useReducer } from 'react';
|
||||
import { PRODUCTS_ACTIONS, productsReducer } from './reducer';
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef } from 'react';
|
||||
|
||||
import { createInitialState } from './initialState';
|
||||
import { PRODUCTS_ACTIONS, productsReducer } from './reducer';
|
||||
|
||||
const ProductsStateContext = createContext(null);
|
||||
import { createProduct, deleteProduct, fetchProducts, getApiErrorMessage, patchProduct, updateProduct } from './api';
|
||||
|
||||
// Zeitverzögerung für das Debouncing (in ms), bevor Änderungen an den Server gesendet werden.
|
||||
const PERSIST_DELAY_MS = 400;
|
||||
|
||||
export const ProductsStateContext = createContext(null);
|
||||
const ProductsDispatchContext = createContext(null);
|
||||
|
||||
// const ProductsContext = createContext(null);
|
||||
const ProductsActionsContext = createContext(null);
|
||||
|
||||
export const ProductsProvider = (props) => {
|
||||
const { children, items = [] } = props;
|
||||
const { children, items = [], autoLoad = true } = props;
|
||||
|
||||
const [state, dispatch] = useReducer(productsReducer, items, createInitialState);
|
||||
const [state, dispatch] = useReducer(
|
||||
productsReducer,
|
||||
items,
|
||||
createInitialState, // Lazy Initialization: Wird nur beim ersten Mounten ausgeführt.
|
||||
);
|
||||
|
||||
/**
|
||||
* WARUM `useRef(new Map())` FÜR PATCHES & TIMER?
|
||||
*
|
||||
* 1. Überleben von Rerendern: `useRef` behält Werte über Renders hinweg stabil,
|
||||
* ohne bei einer Änderung ein neues Rerendern der Komponente auszulösen.
|
||||
* 2. Granularität pro Produkt: Da wir `Map` nutzen (Key = Product-ID), verwalten wir
|
||||
* ausstehende Patches und Timer isoliert pro Item. Ein Ändern von Produkt A bricht
|
||||
* den Timer für Produkt B nicht ab.
|
||||
* 3. Kein API-Spam (Debouncing + Merging): Schnelle Änderungen (z. B. Tippen im Input-Feld)
|
||||
* werden lokal gepuffert und erst nach Ablauf der Inaktivitätszeit als gebündelter Request gesendet.
|
||||
*/
|
||||
const pendingPatches = useRef(new Map()); // Speichert: Map<productId, { stock?: number, price?: number }>
|
||||
const persistTimers = useRef(new Map()); // Speichert: Map<productId, setTimeoutId>
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoLoad) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const load = async () => {
|
||||
dispatch({ type: PRODUCTS_ACTIONS.SET_LOADING, payload: true });
|
||||
|
||||
try {
|
||||
const loadedItems = await fetchProducts({ signal: controller.signal });
|
||||
dispatch({ type: PRODUCTS_ACTIONS.SET_ITEMS, payload: loadedItems });
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.SET_ERROR,
|
||||
payload: getApiErrorMessage(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
|
||||
return () => {
|
||||
controller.abort(); // Bricht laufende Requests ab, falls die Komponente unmountet.
|
||||
};
|
||||
}, [autoLoad]);
|
||||
|
||||
const loadProducts = useCallback(async () => {
|
||||
dispatch({ type: PRODUCTS_ACTIONS.SET_LOADING, payload: true });
|
||||
|
||||
try {
|
||||
const loadedItems = await fetchProducts();
|
||||
dispatch({ type: PRODUCTS_ACTIONS.SET_ITEMS, payload: loadedItems });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.SET_ERROR,
|
||||
payload: getApiErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* `flushPatch(id)`
|
||||
*
|
||||
* Zweck: Führt das tatsächliche Senden der gepufferten Änderungen an das Backend aus.
|
||||
*
|
||||
* Ablauf:
|
||||
* 1. Liest und entfernt den gepufferten Patch für die Produkt-ID aus `pendingPatches`.
|
||||
* 2. Löscht ggf. den aktiven Timer für dieses Produkt aus `persistTimers`.
|
||||
* 3. Sendet den `patchProduct`-Request an die API.
|
||||
* 4. Bei Erfolg: Aktualisiert das Produkt im lokalen Reducer-State mit den Server-Daten.
|
||||
* 5. Bei Fehler: Setzt eine Fehlermeldung und ruft `loadProducts()` auf, um inkonsistente
|
||||
* optimistische UI-Zustände mit dem Serverzustand zu synchronisieren (Rollback).
|
||||
*/
|
||||
const flushPatch = useCallback(
|
||||
async (id) => {
|
||||
const patch = pendingPatches.current.get(id);
|
||||
pendingPatches.current.delete(id);
|
||||
|
||||
if (persistTimers.current.has(id)) {
|
||||
clearTimeout(persistTimers.current.get(id));
|
||||
persistTimers.current.delete(id);
|
||||
}
|
||||
|
||||
if (!patch || Object.keys(patch).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const updated = await patchProduct(id, patch);
|
||||
dispatch({ type: PRODUCTS_ACTIONS.SET_ITEM, payload: updated });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.SET_ERROR,
|
||||
payload: getApiErrorMessage(error),
|
||||
});
|
||||
await loadProducts();
|
||||
}
|
||||
},
|
||||
[loadProducts],
|
||||
);
|
||||
|
||||
/**
|
||||
* `schedulePatch(id, patch)`
|
||||
*
|
||||
* Zweck: Plant das verzögerte Speichern ein (Debounce) und aggregiert Teildaten.
|
||||
*
|
||||
* Ablauf:
|
||||
* 1. Merged neue Teiländerungen mit bereits ausstehenden Änderungen für diese ID
|
||||
* (z. B. `{ stock: 5 }` + `{ price: 10 }` => `{ stock: 5, price: 10 }`).
|
||||
* 2. Setzt einen eventuell laufenden Timeout für dieses Produkt zurück (Reset Debounce-Timer).
|
||||
* 3. Startet einen neuen Timer über `PERSIST_DELAY_MS` (400 ms), nach dessen Ablauf `flushPatch(id)` aufgerufen wird.
|
||||
*/
|
||||
const schedulePatch = useCallback(
|
||||
(id, patch) => {
|
||||
const merged = { ...(pendingPatches.current.get(id) || {}), ...patch };
|
||||
pendingPatches.current.set(id, merged);
|
||||
|
||||
if (persistTimers.current.has(id)) {
|
||||
clearTimeout(persistTimers.current.get(id));
|
||||
}
|
||||
|
||||
persistTimers.current.set(
|
||||
id,
|
||||
setTimeout(() => {
|
||||
flushPatch(id);
|
||||
}, PERSIST_DELAY_MS),
|
||||
);
|
||||
},
|
||||
[flushPatch],
|
||||
);
|
||||
|
||||
/**
|
||||
* Unmount-Cleanup:
|
||||
* Wenn der Provider unmountet wird (z. B. Seitenwechsel), werden alle offenen Timer gecancelt
|
||||
* und noch ausstehende Patches sofort "best-effort" an die API geschickt, damit keine Daten verloren gehen.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const timers = persistTimers.current;
|
||||
const patches = pendingPatches.current;
|
||||
|
||||
return () => {
|
||||
timers.forEach((timer) => clearTimeout(timer));
|
||||
timers.clear();
|
||||
|
||||
patches.forEach((patch, id) => {
|
||||
if (patch && Object.keys(patch).length > 0) {
|
||||
patchProduct(id, patch).catch(() => {});
|
||||
}
|
||||
});
|
||||
patches.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* `updateAmount` & `updatePrice`:
|
||||
* Setzen auf Optimistic UI.
|
||||
* 1. Der UI-State wird sofort synchron via `dispatch` aktualisiert (keine Eingabelatenz für den User).
|
||||
* 2. Das Backend-Update wird verzögert via `schedulePatch` eingereiht.
|
||||
*/
|
||||
const updateAmount = useCallback(
|
||||
(amount, id) => {
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.UPDATE_AMOUNT,
|
||||
payload: { id, amount },
|
||||
});
|
||||
|
||||
const stock = Number(amount);
|
||||
if (Number.isFinite(stock)) {
|
||||
schedulePatch(id, { stock });
|
||||
}
|
||||
},
|
||||
[schedulePatch],
|
||||
);
|
||||
|
||||
const updatePrice = useCallback(
|
||||
(price, id) => {
|
||||
dispatch({ type: PRODUCTS_ACTIONS.UPDATE_PRICE, payload: { id, price } });
|
||||
|
||||
const numericPrice = Number(price);
|
||||
if (Number.isFinite(numericPrice)) {
|
||||
schedulePatch(id, { price: numericPrice });
|
||||
}
|
||||
},
|
||||
[schedulePatch],
|
||||
);
|
||||
|
||||
const addProduct = useCallback(async (product) => {
|
||||
try {
|
||||
const created = await createProduct(product);
|
||||
dispatch({ type: PRODUCTS_ACTIONS.ADD_ITEM, payload: created });
|
||||
return created;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.SET_ERROR,
|
||||
payload: getApiErrorMessage(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const saveProduct = useCallback(async (id, product) => {
|
||||
try {
|
||||
const updated = await updateProduct(id, product);
|
||||
dispatch({ type: PRODUCTS_ACTIONS.SET_ITEM, payload: updated });
|
||||
return updated;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.SET_ERROR,
|
||||
payload: getApiErrorMessage(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const removeProduct = useCallback(async (id) => {
|
||||
try {
|
||||
await deleteProduct(id);
|
||||
dispatch({ type: PRODUCTS_ACTIONS.REMOVE_ITEM, payload: id });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCTS_ACTIONS.SET_ERROR,
|
||||
payload: getApiErrorMessage(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Memoisierung der Action-Funktionen, damit Konsumenten des Actions-Contexts
|
||||
// nicht bei jeder State-Änderung unnötig neu rendern.
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
loadProducts,
|
||||
updateAmount,
|
||||
updatePrice,
|
||||
addProduct,
|
||||
saveProduct,
|
||||
removeProduct,
|
||||
}),
|
||||
[loadProducts, updateAmount, updatePrice, addProduct, saveProduct, removeProduct],
|
||||
);
|
||||
|
||||
return (
|
||||
// <ProductsContext.Provider value={{state, dispatch}}>
|
||||
// {children}
|
||||
// </ProductsContext.Provider>
|
||||
|
||||
<ProductsStateContext.Provider value={state}>
|
||||
<ProductsDispatchContext.Provider value={dispatch}>{children}</ProductsDispatchContext.Provider>
|
||||
<ProductsDispatchContext.Provider value={dispatch}>
|
||||
<ProductsActionsContext.Provider value={actions}>{children}</ProductsActionsContext.Provider>
|
||||
</ProductsDispatchContext.Provider>
|
||||
</ProductsStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// eigene Hooks mit ErrorHandler
|
||||
export const useProductsState = () => {
|
||||
const state = useContext(ProductsStateContext);
|
||||
|
||||
if (state == null) {
|
||||
throw Error('useProductsState muss innerhalb von ProductsProvider verwendet werden.');
|
||||
throw new Error('useProductsState muss innerhalb von ProductsProvider verwendet werden');
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -38,32 +285,18 @@ export const useProductsDispatch = () => {
|
||||
const dispatch = useContext(ProductsDispatchContext);
|
||||
|
||||
if (dispatch == null) {
|
||||
throw Error('useProductsDispatch muss innerhalb von ProductsProvider verwendet werden.');
|
||||
throw new Error('useProductsDispatch muss innerhalb von ProductsProvider verwendet werden');
|
||||
}
|
||||
|
||||
return dispatch;
|
||||
};
|
||||
|
||||
// Factory Function
|
||||
export const useProductsActions = () => {
|
||||
const dispatch = useProductsDispatch();
|
||||
const actions = useContext(ProductsActionsContext);
|
||||
|
||||
const updateAmount = useCallback(
|
||||
(amount, id) => {
|
||||
dispatch({ type: PRODUCTS_ACTIONS.UPDATE_AMOUNT, payload: { id, amount } });
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
if (actions == null) {
|
||||
throw new Error('useProductsActions muss innerhalb von ProductsProvider verwendet werden');
|
||||
}
|
||||
|
||||
const updatePrice = useCallback(
|
||||
(price, id) => {
|
||||
dispatch({ type: PRODUCTS_ACTIONS.UPDATE_PRICE, payload: { id, price } });
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return {
|
||||
updateAmount,
|
||||
updatePrice,
|
||||
};
|
||||
return actions;
|
||||
};
|
||||
|
||||
54
webseite-react-php/react-app/src/stores/products/api.js
vendored
Normal file
54
webseite-react-php/react-app/src/stores/products/api.js
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const productsClient = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL ?? '/api',
|
||||
timeout: 8000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
export const getApiErrorMessage = (error) => {
|
||||
if (axios.isCancel(error) || error.code === 'ERR_CANCELED') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return error.response?.data?.error || error.message || 'Unbekannter API Fehler';
|
||||
};
|
||||
|
||||
export const fetchProducts = async (config = {}) => {
|
||||
const { data } = await productsClient.get('/products', config);
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Ungültige API-Antwort: Produktliste wird erwartet.');
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchProduct = async (id, config = {}) => {
|
||||
const { data } = await productsClient.get(`/products/${id}`, config);
|
||||
|
||||
if (!Object.hasOwn(data, _id)) {
|
||||
throw new Error('Ungültige API-Antwort: Produkt wird erwartet. (keine id)');
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createProduct = async (product) => {
|
||||
const { data } = await productsClient.post('/products', product);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateProduct = async (id, product) => {
|
||||
const { data } = await productsClient.put(`/products/${id}`, product);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const patchProduct = async (id, patch) => {
|
||||
const { data } = await productsClient.patch(`/products/${id}`, patch);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteProduct = async (id) => {
|
||||
await productsClient.delete(`/products/${id}`);
|
||||
};
|
||||
@@ -1,3 +1,12 @@
|
||||
export { createInitialState } from './initialState';
|
||||
export { PRODUCTS_ACTIONS, productsReducer } from './reducer';
|
||||
export { ProductsProvider, useProductsActions, useProductsDispatch, useProductsState } from './ProductsStore';
|
||||
export {
|
||||
createProduct,
|
||||
deleteProduct,
|
||||
fetchProduct,
|
||||
fetchProducts,
|
||||
getApiErrorMessage,
|
||||
patchProduct,
|
||||
updateProduct,
|
||||
} from './api';
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// import mit {}
|
||||
export const createInitialState = (items) => {
|
||||
// validation and sanitizing
|
||||
return { items: items || [] };
|
||||
return {
|
||||
items: items || [], //
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
};
|
||||
|
||||
export default createInitialState; // import mit eigener Benennung möglich
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// in ts enum
|
||||
export const PRODUCTS_ACTIONS = {
|
||||
SET_LOADING: 'SET_LOADING',
|
||||
SET_ITEMS: 'SET_ITEMS',
|
||||
SET_ITEM: 'SET_ITEM',
|
||||
SET_ERROR: 'SET_ERROR',
|
||||
ADD_ITEM: 'ADD_ITEM',
|
||||
REMOVE_ITEM: 'REMOVE_ITEM',
|
||||
UPDATE_AMOUNT: 'UPDATE_AMOUNT',
|
||||
UPDATE_PRICE: 'UPDATE_PRICE',
|
||||
};
|
||||
@@ -7,25 +13,59 @@ export const PRODUCTS_ACTIONS = {
|
||||
export const productsReducer = (state, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
console.log(state);
|
||||
|
||||
switch (type) {
|
||||
case PRODUCTS_ACTIONS.SET_LOADING:
|
||||
return {
|
||||
...state,
|
||||
loading: payload,
|
||||
error: payload ? null : state.error,
|
||||
};
|
||||
case PRODUCTS_ACTIONS.SET_ITEMS:
|
||||
return {
|
||||
...state,
|
||||
items: payload,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
case PRODUCTS_ACTIONS.SET_ITEM:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.map((item) => (item._id === payload._id ? payload : item)),
|
||||
error: null,
|
||||
};
|
||||
case PRODUCTS_ACTIONS.SET_ERROR:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: payload,
|
||||
};
|
||||
case PRODUCTS_ACTIONS.ADD_ITEM:
|
||||
return {
|
||||
...state,
|
||||
items: [...state.items, payload],
|
||||
error: null,
|
||||
};
|
||||
case PRODUCTS_ACTIONS.REMOVE_ITEM:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.filter((item) => item._id !== payload),
|
||||
error: null,
|
||||
};
|
||||
case PRODUCTS_ACTIONS.UPDATE_AMOUNT:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.map((item) =>
|
||||
item._id === payload.id //
|
||||
? { ...item, stock: payload.amount }
|
||||
item._id === payload.id
|
||||
? { ...item, stock: payload.amount } //
|
||||
: item,
|
||||
),
|
||||
};
|
||||
|
||||
case PRODUCTS_ACTIONS.UPDATE_PRICE:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.map((item) =>
|
||||
item._id === payload.id //
|
||||
? { ...item, price: payload.price }
|
||||
item._id === payload.id
|
||||
? { ...item, price: payload.price } //
|
||||
: item,
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user