Tengo un problema que no puedo resolver. Estoy creando una aplicación de reacción de comercio electrónico y uso useReducer y useContext para la administración del estado. El cliente abre un producto, elige una cantidad de artículos y luego hace clic en el botón "Agregar al carrito" que envía una acción. Esta parte está funcionando bien y comienza el problema. No sé cómo mostrar y actualizar en el componente Navbar.js una cantidad total de productos en el carrito. Se muestra después de los cambios de ruta, pero quiero que se actualice al hacer clic en el botón Agregar al carrito. Probé useEffect pero no funciona.
el estado inicial se ve así
const initialState = [ { productName: '', count: 0 } ]AddToCart.js funciona bien
import React, { useState, useContext } from 'react' import { ItemCounterContext } from '../../App' function AddToCart({ product }) { const itemCounter = useContext(ItemCounterContext) const [countItem, setCountItem] = useState(0) const changeCount = (e) => { if (e === '+') { setCountItem(countItem + 1) } if (e === '-' && countItem > 0) { setCountItem(countItem - 1) } } return ( <div className='add margin-top-small'> <div className='add-counter' onClick={(e) => changeCount(e.target.innerText)} role='button' > - </div> <div className='add-counter'>{countItem}</div> <div className='add-counter' onClick={(e) => changeCount(e.target.innerText)} role='button' > + </div> <button className='add-btn btnOrange' onClick={() => itemCounter.dispatch({ type: 'addToCart', productName: product.name, count: countItem })} > Add to Cart </button> </div> ) } export default AddToCartNavbar.js es donde tengo un problema
import React, { useContext } from 'react' import { Link, useLocation } from 'react-router-dom' import NavList from './NavList' import { StoreContext, ItemCounterContext } from '../../App' import Logo from '../Logo/Logo' function Navbar() { const store = useContext(StoreContext) const itemCounter = useContext(ItemCounterContext) const cartIcon = store[6].cart.desktop const location = useLocation() const path = location.pathname const itemsSum = itemCounter.state .map((item) => item.count) .reduce((prev, curr) => prev + curr, 0) const totalItemsInCart = ( <span className='navbar__elements-sum'> {itemsSum} </span> ) return ( <div className={`navbar ${path === '/' ? 'navTransparent' : 'navBlack'}`}> <nav className='navbar__elements'> <Logo /> <NavList /> <Link className='link' to='/cart'> <img className='navbar__elements-cart' src={cartIcon} alt='AUDIOPHILE CART ICON' /> {itemsSum > 0 ? totalItemsInCart : null} </Link> </nav> </div> ) } export default NavbarBueno, ItemCounterContext es importante para este problema, simplemente ignore StoreContext, es para imágenes... Aquí hay una función reductora.
export const reducer = (state, action) => { // returns -1 if product doesn't exist const indexOfProductInCart = state.findIndex((item) => item.productName === action.productName) const newState = state switch (action.type) { case 'increment': { if (indexOfProductInCart === -1) { newState[state.length] = { productName: action.productName, count: state.count + 1 } return newState } newState[indexOfProductInCart] = { productName: action.productName, count: state.count + 1 } return newState } case 'decrement': { if (indexOfProductInCart === -1) { newState[state.length] = { productName: action.productName, count: state.count - 1 } return newState } newState[indexOfProductInCart] = { productName: action.productName, count: state.count - 1 } return newState } case 'addToCart': { if (indexOfProductInCart === -1) { newState[state.length] = { productName: action.productName, count: action.count } return newState } newState[indexOfProductInCart] = { productName: action.productName, count: action.count } return newState } case 'remove': return state.splice(indexOfProductInCart, 1) default: return state } }Y aquí está App.js donde comparto el estado con otros componentes
import React, { createContext, useMemo, useReducer } from 'react' import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import Navbar from './components/Navbar/Navbar' import Homepage from './pages/Homepage/Homepage' import Footer from './components/Footer/Footer' import ErrorPage from './pages/ErrorPage/ErrorPage' import SelectedCategory from './pages/SelectedCategory/SelectedCategory' import SingleProduct from './pages/SingleProduct/SingleProduct' import ScrollToTop from './services/ScrollToTop' import store from './services/data.json' import { reducer } from './services/ItemCounter' import './scss/main.scss' export const StoreContext = createContext(store) export const ItemCounterContext = createContext() function App() { const initialState = [{ productName: '', count: 0 }] const [state, dispatch] = useReducer(reducer, initialState) const counter = useMemo(() => ({ state, dispatch }), []) return ( <div className='app'> <StoreContext.Provider value={store}> <ItemCounterContext.Provider value={counter}> <Router> <ScrollToTop /> <Navbar /> <Routes> <Route path='/' element={<Homepage />} /> <Route path='/:selectedCategory' element={<SelectedCategory />} /> <Route path='/:selectedCategory/:singleProduct' element={<SingleProduct />} /> <Route path='*' element={<ErrorPage />} /> </Routes> <Footer /> </Router> </ItemCounterContext.Provider> </StoreContext.Provider> </div> ) } export default AppEl problema está en su reductor, particularmente donde asigna el estado anterior al estado newState para hacer mutaciones y devolver el estado actualizado. En JavaScript, los no primitivos se refieren por dirección y no por valor. Dado que su initialState que es una matriz, no es primitivo, por lo que cuando asigna un no primitivo a una nueva variable, esta variable solo apunta a la matriz existente en la memoria y no crea una nueva copia. Y, en React, las actualizaciones se activan/transmiten solo cuando se reconstruye un estado (así es como React entiende que hay una actualización) y no muta suavemente. Cuando muta y devuelve newState , básicamente está mutando el state existente y no haciendo que se reconstruya. Una solución rápida para esto sería copiar su state en newState y no simplemente asignarlo. Esto podría hacerse usando el operador de propagación ( ... ).
En su función reductora, cambie:
const newState = statepara
const newState = [...state]Su función reductora debería verse así:
export const reducer = (state, action) => { // returns -1 if product doesn't exist const indexOfProductInCart = state.findIndex((item) => item.productName === action.productName) const newState = [...state] //Deep-copying the previous state switch (action.type) { case 'increment': { if (indexOfProductInCart === -1) { newState[state.length] = { productName: action.productName, count: state.count + 1 } return newState } newState[indexOfProductInCart] = { productName: action.productName, count: state.count + 1 } return newState } case 'decrement': { if (indexOfProductInCart === -1) { newState[state.length] = { productName: action.productName, count: state.count - 1 } return newState } newState[indexOfProductInCart] = { productName: action.productName, count: state.count - 1 } return newState } case 'addToCart': { if (indexOfProductInCart === -1) { newState[state.length] = { productName: action.productName, count: action.count } return newState } newState[indexOfProductInCart] = { productName: action.productName, count: action.count } return newState } case 'remove': return state.splice(indexOfProductInCart, 1) default: return state } }Sé exactamente de qué estás hablando, pero el problema en el reductor es que solo los métodos mutativos funcionan en el estado. Los métodos inmutables como .slice(), .concat() o incluso el operador de propagación [...state] no funcionan y no sé por qué :( probé ambas respuestas, pero dispatch(action) no funciona cambiar el estado Tal vez el estado inicial es el problema, intentaré ponerlo así
estadoInicial = { carrito: [nombreProducto: '', cuenta: 0] }