The reducer function works fine except that previously added object element to the item
array property gets over written whenever new object element is added.
For example, if state.item
contains {number: 1}
and I add {number: 2}
, it becomes [{number: 2},{number: 2}]
instead of [{number: 1},{number: 2}]
.
The reducer function:
const reducer = (state, action) => {
if (action.type === "ADD") {
let newItem = state.item.concat(action.addItem);
console.log("action.addItem:", action.addItem);
console.log("state.item:", state.item);
return { item: newItem };
}
};
Is there any solution this problem?
Thank You.
Parent Component:
import React from "react";
import { useReducer } from "react";
import { CreateContext } from "./CreateContext";
const initialState = {
item: [],
};
const reducer = (state, action) => {
if (action.type === "ADD") {
let newItem = state.item.concat(action.addItem);
console.log("action.addItem:", action.addItem);
console.log("state.item:", state.item);
return { item: newItem };
}
};
const AuthProvider = (props) => {
// define useReducer
const [state, dispatch] = useReducer(reducer, initialState);
// define handlers
const addItemHandler = (addItem) => {
console.log("addItemHandler");
dispatch({ type: "ADD", addItem: addItem });
};
const data = {
addItem: addItemHandler,
number: 0,
item: state.item,
};
return (
<CreateContext.Provider value={data}>
{props.children}
</CreateContext.Provider>
);
};
export default AuthProvider;
Child component:
import React, { useState, useContext } from "react";
import {
Button,
Card,
CardActionArea,
CardActions,
CardContent,
CardMedia,
makeStyles,
Typography,
Collapse,
TextField,
IconButton,
} from "@material-ui/core";
import clsx from "clsx";
import AddBoxIcon from "@material-ui/icons/AddBox";
import { Grid } from "@material-ui/core";
import { CreateContext } from "../Store/CreateContext";
const useStyles = makeStyles((theme) => ({
card: {
marginBottom: theme.spacing(5),
},
media: {
height: 250,
// smaller image for mobile
[theme.breakpoints.down("sm")]: {
height: 150,
},
},
priceDetail: {
marginLeft: theme.spacing(15),
},
numberTextField: {
width: 52,
},
addBtn: {
fontSize: 60,
},
}));
const data = {
id: null,
name: null,
price: null,
quantity: null,
};
// img and title from the feed component
const Food = ({ img, title, description, price, id }) => {
const classes = useStyles();
// expand the description
const [expanded, setExpanded] = React.useState(false);
const handleExpandClick = () => {
setExpanded(!expanded);
};
const newPrice = `RM${price.toFixed(2)} `;
////// process the form //////
//get quantity from TextField
const [quantity, setQuantity] = useState("");
const quantityHandler = (enteredQuantity) => {
console.log("enteredQuantity:", enteredQuantity);
setQuantity(enteredQuantity.target.value);
};
// use useContext
const AuthData = useContext(CreateContext);
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
return (
<Grid item xs={12} md={6}>
<form onSubmit={submitHandler}>
<Card className={classes.card} id={id}>
<CardActionArea>
<CardMedia className={classes.media} image={img} title="My Card" />
<CardContent>
<Typography gutterBottom variant="h5">
{title}
</Typography>
<Button
size="small"
color="primary"
className={clsx(classes.expand, {
[classes.expandOpen]: expanded,
})}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
Learn More
</Button>
<CardActionArea>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography paragraph>{description}</Typography>
</CardContent>
</Collapse>
</CardActionArea>
</CardContent>
</CardActionArea>
<CardActions>
{" "}
<Typography variant="h6" className={classes.priceDetail}>
{newPrice}
</Typography>
<Typography variant="h6" className={""}>
x
</Typography>
<TextField
id={id}
label="amount"
type="number"
// value={}
// onChange={}
className={classes.numberTextField}
label=""
variant="outlined"
min="1"
max="5"
step="1"
defaultValue="0"
size="small"
onChange={quantityHandler}
input={id}
// ref={quantity}
/>
<IconButton aria-label="" onClick={""} type="submit">
<AddBoxIcon
color="secondary"
className={classes.addBtn}
></AddBoxIcon>
</IconButton>
</CardActions>
</Card>
</form>
</Grid>
);
};
export default Food;
The problem is entirely in your child component, and nothing to do with your reducer. It's how you are passing in the data that becomes the addItem
payload in the action you dispatch.
I have reproduced below the relevant parts of the child component (or rather the whole module), so you can see the problem more clearly:
const data = {
id: null,
name: null,
price: null,
quantity: null,
};
const Food = ({ img, title, description, price, id }) => {
// more code that isn't relevant here
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
// more code that isn't relevant here
}
The data
that you are passing to AuthData.addItem
(that ends up being passed to the reducer) isn't a new object each time - it's a single "global" (module-level) constant that you simply mutate each time you use it. This is what's causing your state to mutate - because each object that ends up in the array of your state.items
is a reference to that same object, so each mutation you make (inside submitHandler
) ends up changing every copy!
You can easily fix this in the child component, by not simply mutating the same object each time but recreating a new one. I don't see any reason for what you are doing, so simply stop doing it! I would rewrite it as below:
// note NO const data = ...
const Food = ({ img, title, description, price, id }) => {
// more code that isn't relevant here
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
const data = {};
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
// more code that isn't relevant here
}
Doing it this way, each object in the state "item" array will be unique, and you can dispatch new ones without affecting the old ones!