0
votes

I'm a react newbie and im trying to build a simple shopping cart app without the use of class components after finding out about hooks. In my top level Component App I get a list of items via an imported json, then create a state with useState for the item list and also build a reducer to add or remove items from the cart which has its own state. Each state is being passed into a component, the ItemList together with a function to dispatch the cart state change (SpeiseListe in code) and the Cart. The ItemList maps the list of items to single Item Components, which have a button which onClick triggers said dispatch and the cart hook state in the top level component gets updated. Now the problem here is that the whole App rerenders, and so does the ItemList, on every action involving the cart. I would like to update only the Cart Component, but I'm not sure about the right approach.

(https://i.imgur.com/N6pfjvu.png)

I'm not sure what the solution or a concept to a solution could be. I was thinking of creating the states in ItemList (SpeiseListe) and Cart respectively, but then the Item Component would need to communicate with its parents sibling and that shouldnt be right?

App

import React, {useState, useReducer} from 'react'
import styled from 'styled-components'

import './bootstrap.min.css'

import Speisen from './Speisen'
import Cart from './Cart'

import * as speisen from './speisen.json'


export default function App() {
    const cartHandlerReducer = (cart, action) => {
        switch (action.type) {
            case 'ADD_TO_CART':
                return cart.concat(action.cartItem)
            case 'REMOVE_FROM_CART':
                return cart.filter(item => item.speise.id !== action.cartItem.speise.id)
            default:
                return cart
        }
    }

    const speisenNumbered = speisen.default.speisen.map((item, i) => ({...item, itemOrder: i + 1}))

    const [cart, dispatch] = useReducer(cartHandlerReducer, [])
    const [speiseListe, setSpeiseListe] = useState(speisenNumbered)

    const cartHandler = (cartItem) => {
        if (cart.find(item => item.speise.id === cartItem.speise.id)) {
            dispatch({type: 'REMOVE_FROM_CART', cartItem})
        } else {
            dispatch({type: 'ADD_TO_CART', cartItem})
        }
    }

    const MainContainer = styled.div.attrs({
        className: 'container'
    })``

    const Row = styled.div.attrs({
        className: 'row'
    })``

    return (
        <MainContainer>
            <Row>
                <Speisen
                    speisen={speiseListe}
                    cartHandler={cartHandler}
                />
                <Cart
                    cart={cart}
                />
            </Row>
        </MainContainer>
    )
}

Single Item Component

import React, {useState} from 'react'
import styled from 'styled-components'


function Button(props) {
    return (
        <button
            onClick={props.onClick}
        >
            {props.added ?
                'remove' :
                'add'
            }
        </button>
    )
}


const SpeiseContainer = styled.div.attrs({
    className: 'row'
})`
    margin-bottom: 60px;
`

const OrderCol = styled.div.attrs({
    className: 'col-1'
})``

const NameCol = styled.div.attrs({
    className: 'col'
})``

const PreisCol = styled.div.attrs({
    className: 'col-2'
})`
    text-align: right;
`

const ButtonCol = styled.div.attrs({
    className: 'col-auto'
})``

export default function Speise (props) {
    const {speise, accomps, cartHandler} = props

    const [currentAccomp, setCurrentAccomp] = useState(accomps && accomps[0].id)

    const changeCurrentAccomp = (event) => setCurrentAccomp(accomps[event.target.value].id)

    const cartItemButton = () => {
        cartHandler({speise, accomps, currentAccomp})
    }

    console.log(currentAccomp)

    return (
        <SpeiseContainer>
            <OrderCol>
                {speise.itemOrder}
            </OrderCol>
            <NameCol>
                {speise.name}
                {currentAccomp && (
                    accomps.length > 1 ? (
                        <select
                            onChange={changeCurrentAccomp}
                        >
                            {accomps.map((accomp, i) => 
                                <option
                                    key={accomp.id}
                                    value={i}
                                >
                                    {accomp.name}
                                </option>
                            )}
                        </select>
                    ):(
                        accomps[0].name
                    )
                )}
            </NameCol>
            <PreisCol>
                {speise.price}
            </PreisCol>
            <ButtonCol>
                <Button onClick={cartItemButton} />
            </ButtonCol>
        </SpeiseContainer>
    )
}

I would like to implement a state management using Hooks which makes it possible to add items to the shopping cart without rerendering the item list itself just because both states are being managed in the same component or a different concept of how to do this at all.

Thanks a lot!

1

1 Answers

0
votes

If the item list data is not changing at all, you can use React.memo to wrap the ItemList component, which will perform a shallow comparison of the previous and new props and prevent a re-render if they are equal. So inside your Speisen component you can simply export it like this:

export default React.memo(Speisen);