Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

289
Visualizações
useReducer: dispatch action, show state in other component and update state when action is dispatched

I have a problem which I can't figure it out. I'm building an ecommerce react app and using useReducer and useContext for state management. Client opens a product, picks number of items and then click button "Add to Cart" which dispatches an action. This part is working well, and the problem starts. I don't know how to show and update in Navbar.js component a total number of products in cart. It is showing after route changes, but I want it to update when clicking Add to Cart button. I tried useEffect but it doesn't work.

initial state looks like this

const initialState = [
  {
    productName: '',
    count: 0
  }
]

AddToCart.js works good

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 AddToCart

Navbar.js is where I have a problem

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 Navbar
over 4 years ago · Santiago Trujillo
3 Respostas
Responde à pergunta

0

Well, ItemCounterContext is important for this problem, just ignore StoreContext, it's for images... Here is a reducer function.

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
  }
}

And here is App.js where I share state to other components

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 App
over 4 years ago · Santiago Trujillo Relatório

0

The problem is in your reducer, particularly where you assign the previous state to the newState to make mutations and return the updated state. In JavaScript, non-primitive are referred by address and not by value. Since your initialState which is an array happens to be a non-primitive, so when you assign a non-primitive to a new variable, this variable only points to the existing array in memory and does not create a new copy. And, in react updates are triggered/broadcasted only when a state is reconstructed (that's how React understands that there is an update) and not softly mutated. When you mutate and return newState, you are basically mutating the existing state and not causing it to reconstruct. A quick workaround for this would be to copy over your state into newState and not merely assign it. This could be done using the spread operator(...).
In your reducer function, change:

const newState = state

to

const newState = [...state]

Your reducer function should then look something like this:

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
  }
}
over 4 years ago · Santiago Trujillo Relatório

0

I know exactly what are you talking about, but the problem in reducer is that only mutative methods works on state. Immutable methods like .slice(), .concat() or even spread operator [...state] doesn't work and I don't know why :( I tried both of the answers, but dispatch(action) doesn't change the state. Maybe initial state is the problem, I'll try to put it like

initialState = { cart: [ productName: '', count: 0 ] }

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda