I’ve built an e-commerce app and need the cart items to stay constant if a user refreshes the page. I have created a slice in this file where all of the actions are. I was doing some reading on redux-tooklit-persist and it sounds like that might be my solution. I’ve included the reducer file as well as the file which contains the "AddToCart" function.
import { createSlice } from "@reduxjs/toolkit"
const initialState = {
isCartOpen: false,
cart: [],
items: []
}
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
setItems: (state, action) => {
state.items = action.payload
},
// Add item to cart
addToCart: (state, action) => {
state.cart = [...state.cart, action.payload.item]
},
// Remove item from cart
removeFromCart: (state, action) => {
state.cart = state.cart.filter((item) => item.id !== action.payload.id)
},
// Increase count in cart
increaseCount: (state, action) => {
state.cart = state.cart.map((item) => {
if (item.id === action.payload.id) {
item.count++
}
return item
})
},
// Decrease count in cart
decreaseCount: (state, action) => {
state.cart = state.cart.map((item) => {
if (item.id === action.payload.id && item.count > 1) {
item.count--
}
return item
})
},
setIsCartOpen: (state) => {
state.isCartOpen = !state.isCartOpen
}
}
})
//Export
export const {
setItems,
addToCart,
removeFromCart,
increaseCount,
decreaseCount,
setIsCartOpen
} = cartSlice.actions
export default cartSlice.reducer
Here is my Item.jsx
import { useState } from "react"
import { useDispatch } from "react-redux"
import { IconButton, Box, Typography, useTheme, Button } from "@mui/material"
import AddIcon from "@mui/icons-material/Add"
import RemoveIcon from "@mui/icons-material/Remove"
import { shades } from "../theme"
import { addToCart } from "../state"
import { useNavigate } from "react-router-dom"
const Item = ({ item, width }) => {
const navigate = useNavigate()
const dispatch = useDispatch()
const [count, setCount] = useState(1)
const [isHovered, setIsHovered] = useState(false)
const {
palette: { neutral }
} = useTheme()
// Destructure from attributes
const { category, price, name, image } = item.attributes
const {
data: {
attributes: {
formats: {
medium: { url }
}
}
}
} = image
return (
<Box width={width}>
<Box
position="relative"
onMouseOver={() => setIsHovered(true)}
onMouseOut={() => setIsHovered(false)}
>
<img
alt={item.name}
width="300px"
height="400px"
src={`http://localhost:1337${url}`}
onClick={() => navigate(`/item/${item.id}`)}
style={{ cursor: "pointer" }}
/>
<Box
display={isHovered ? "block" : "none"}
position="absolute"
bottom="10%"
left="0"
width="100%"
padding="0 5%"
>
<Box display="flex" justifyContent="space-between">
<Box
display="flex"
alignItems="center"
backgroundColor={shades.neutral[100]}
borderRadius="3px"
>
<IconButton onClick={() => setCount(Math.max(count - 1, 1))}>
<RemoveIcon />
</IconButton>
<Typography color={shades.primary[300]}>
{count}
</Typography>
<IconButton onClick={() => setCount(count + 1)}>
<AddIcon />
</IconButton>
</Box>
<Button
onClick={() => {
dispatch(addToCart({ item: { ...item, count } }))
}}
sx={{
backgroundColor: shades.primary[300],
color: "white"
}}
>
Add to Cart
</Button>
</Box>
</Box>
</Box>
<Box mt="3px">
<Typography variant="subtitle2" color={neutral.dark}>
{category
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase())
}
</Typography>
<Typography>{name}</Typography>
<Typography fontWeight="bold">${price}</Typography>
</Box>
</Box>
)
}
export default Item
2
Answers
My friend, you have to use localStorage to store your data inside the browser:
Here every time when you dispatch your
addToCart
, your data will save in redux and localStorage and when you refresh the page, your redux will fill with localStorage.I changed just one of those reducers, you have to change all of them based on localStorage and your redux.
If you get stuck, tell me, and I will explain it to you.
redux-persist
is the standard forreact
Redux state persistence.While you could individually initialize and persist state slices from & to localStorage like Mohammad’s answer, this is ill-advised as the act of persisting to localStorage is a side-effect in what is considered to be a pure function.
This would be better abstracted into a custom middleware. The RTK maintainers have made it fairly easy to create middleware listeners with
createListenerMiddleware
. You can create a middleware listener that reacts to specific actions, or conditions, to persist the store to localStorage.I’d suggest just going with
redux-persist
. If you are already familiar withreact
andredux-toolkit
it’s a pretty quick setup/integration.Example:
At this point I’ll also export a wrapper component that renders the
react-redux
Provider
component andredux-persist
‘sPersistGate
component.Now instead of wrapping the
App
component with theProvider
component you can wrap with thePersistedProvider
component instead and provide the Redux store to the app and persist the redux state.