I am experienced javascript developer. However I have to move on react so right now I know few concepts. I've been debugging this code for 4 hours and couldn't find any mistake. I mean, it is probably up to because this hook is a bit confusing.
What app should do? Basically it should display food component based on dummy coded data. It works good. On that newly displayed component should be a form which has input field. When we submit form, that number from the form should be taken and written in a food object. Object should look like this: {Sushi: 0}. Whenever I submit, it should update. Besides that, there isn't just one property in object. There could be more, for example: {Sushi: 0, Pizza: 0, Cherry: 0}. That depends on dummy data. At the begging of the app it automatically creates this object and push into state. Then, I send state updating function to the child component and when we submit form it updated the state. That works fine. Now on the current component, I have useEffect. That use effect trigers whenever state changes. What it does, it updates context. Now when I update context, in "third" component all the values should be taken and sum of the values should bee displayed. The problem is that, whatever I tried, It doesn't re-executes that "third" component...
Code: App.js
function App() {
return (
<FoodStateContext.Provider
value={{
foodState: {},
setContext: function (val) {
this.foodState = val;
},
}}
>
<Header />
<BodyContainer>
<Card className={styles["food-message"]}>
<h2>Delicious Food, Delivered To You</h2>
<p>
Choose your favourite meal from our broad selection of avaible meets
and enjoy a delicious lunch or dinner at home.
</p>
<p>
All our meals are cooked with high-quality ingredients, just-in-time
and of course by experienced chefs!
</p>
</Card>
<FoodContainer foodList={food} />
</BodyContainer>
</FoodStateContext.Provider>
);
}
export default App;
Food container
import react, { useState, useEffect, useReducer, useContext } from "react";
import styles from "./FoodContainer.module.css";
import Card from "../UI/Card/Card";
import FoodComponent from "./FoodComponent";
import FoodStateContext from "../store/food-state-context";
const FoodContainer = (props) => {
const foodList = props.foodList;
const [foodState, setFoodState] = useState({});
const ctx = useContext(FoodStateContext);
useEffect(() => {
let obj = {};
for (let i = 0; i < foodList.length; i++) {
obj[foodList[i].name] = 0;
}
setFoodState(obj);
}, []);
console.log(foodState);
useEffect(() => {
console.log("muda");
ctx.foodState = foodState;
ctx.setContext(foodState);
console.log(ctx.foodState);
}, [foodState]);
return (
<Card className={styles["food-container"]}>
{foodList.map((food) => {
return (
<FoodComponent
key={food.name}
food={food}
updateFoodState={setFoodState}
></FoodComponent>
);
})}
</Card>
); };
export default FoodContainer;
Food component
import React, { useReducer, useEffect, useContext } from "react";
import styles from "./FoodComponent.module.css";
import Input from "../UI/Input/Input";
import Button from "../UI/Button/Button";
const FoodComponent = (props) => {
const food = props.food;
const formSubmitHandler = (e) => {
e.preventDefault();
props.updateFoodState((prevState) => {
return {
...prevState,
[food.name]: prevState[food.name] + +e.target[0].value,
};
});
};
return (
<div className={styles["food-component"]}>
<div className={styles["food-component__description"]}>
<p>{food.name}</p>
<p>{food.description}</p>
<p>{food.price}</p>
</div>
<form onSubmit={formSubmitHandler}>
<div>
<div>
<span>Amount:</span>
<Input />
</div>
<Button type="submit">+Add</Button>
</div>
</form>
</div>
);
};
export default FoodComponent;
Food context
import react from "react";
const FoodStateContext = react.createContext({
foodState: "",
setContext: (val) => {
console.log(val);
this.foodState = val;
},
});
export default FoodStateContext;
That third component
import React, { useState, useContext, useEffect } from "react";
import styles from "./Cart.module.css";
import FoodStateContext from "../store/food-state-context";
const Cart = (props) => {
const [totalProductsNumber, setTotalProductsNumber] = useState(0);
const ctx = useContext(FoodStateContext);
console.log(ctx.foodState);
useEffect(() => console.log("govna"), [ctx]);
return (
<div className={styles.cart}>
<div>Ic</div>
<p>Your Cart</p>
<div>
<span>{totalProductsNumber}</span>
</div>
</div>
);
};
export default Cart;
This is my solution. I haven't tested that but I have done some changes that should fix your problems and optimize your code.
// FoodStateProvider.js
import React from "react";
export const FoodStateContext = React.useContext({
foodState: {},
setFoodState: () => undefined,
});
// Use a custom provider to handle the context logic
export const FoodStateProvider = ({ children }) => {
const [foodState, setFoodState] = React.useState({});
return (
<FoodStateContext.Provider
value={{
foodState,
setFoodState,
}}
>
{children}
</FoodStateContext.Provider>
);
};
Then use the FoodStateProvider in your app
// App.js
const App = () => (
<FoodStateProvider> ...your components </FoodStateProvider>
)
Then, I have remove the useState in the FoodContainer because now you handle it in the context:
// FoodContainer.js
import React, { useState, useEffect, useContext } from "react";
import styles from "./FoodContainer.module.css";
import Card from "../UI/Card/Card";
import FoodComponent from "./FoodComponent";
import FoodStateContext from "../store/food-state-context";
const FoodContainer = ({ foodList }) => {
const ctx = useContext(FoodStateContext);
// The useEffect don't works well here, using a callback helps to remove
// additional data structures and conditions problems
const setFoodState = useCallback(({name, valueToAdd}) => {
// Logic from FoodComponent now is here
const newValue = (ctx.foodState[name] ?? 0) + valueToAdd;
ctx.setFoodState(currentFoodState => {...currentFoodState, [name]: newValue});
}, [foodState]);
return (
<Card className={styles["food-container"]}>
{foodList.map((food) => (
<FoodComponent
key={food.name}
food={food}
updateFoodState={setFoodState}
/>
))}
</Card>
);
};
// FoodComponent.js
import React, { useReducer, useEffect, useContext } from "react";
import styles from "./FoodComponent.module.css";
import Input from "../UI/Input/Input";
import Button from "../UI/Button/Button";
const FoodComponent = ({ food, updateFoodState }) => {
const formSubmitHandler = (e) => {
e.preventDefault();
updateFoodState({ name: food.name, valueToAdd: e.target[0].value });
};
return (
<div>
your components
<form onSubmit={formSubmitHandler}> your form </form>
</div>
);
};
export default FoodComponent;