I am new to React and learning about states and props. I am following a React Wes Bos course and the teacher is using class components, so I am sort of refactoring as I go along to functional component (for exercise and because I have to learn those).
We are coding an app that is supposed to be a fish restaurant, and at some point, we want to load to the order section some values.
I have two main problems:
1 - When I try to run the method addToOrder(key) manually in the React dev tool by using $r on App.js, I get an error
VM761:1 Uncaught TypeError: $r.addToOrder is not a function
2 - The second issue is that when I click on the button Add To Order, the one that is supposed to update the order{} object, the order object itself does not get updated.
I have been searching for a good half day now and I am not sure what could be wrong.
As a self-check:
- the prop index is passed correctly from to as I can console.log(index) and do get the current one.
I am sorry if I am not explaining myself properly, it's a bit hard to condense into a short post. Do ask questions and clarifications as needed, I'll do my best to provide the correct info.
Here's the two components code:
App
import React from "react";
import { Header } from "./Header";
import { Order } from "./Order";
import { Inventory } from "./Inventory";
import { useState } from "react";
import sampleFishes from "../sample-fishes";
import { Fish } from "./Fish";
export const App = () => {
const [state, setState] = useState({
fishes: {},
order: {},
});
/**
* Structure of the function served in <AddFishForm>
* Making a copy of the state to avoid mutations ...state.fishes
* Date.now() used to assign a unique key
*
*/
const addFish = (fish) => {
const fishes = { ...state.fishes };
fishes[`fish${Date.now()}`] = fish;
setState({
fishes: fishes,
});
};
/**
* Function to display a sample fishes in the list
* Made to avoid manual typing
* Fish data comes from ../sample-fishes
*/
const loadSampleFishes = () => {
setState({ fishes: sampleFishes });
};
/**
* Take a copy of state
* Either add to the order or update the number in order
* (if order exists, adds one to it, if not, set it to one)
* Call setState() to update state object
*/
const addToOrder = (key) => {
const order = { ...state.order };
order[key] = order[key] + 1 || 1;
setState({
order: order,
});
};
return (
<div className="catch-of-the-day">
<div className="menu">
<Header tagline="Fresh Seafood Market" />
<ul className="fishes">
{Object.keys(state.fishes).map((key) => {
return (
<Fish
key={key}
details={state.fishes[key]}
addToOrder={addToOrder}
></Fish>
);
})}
</ul>
</div>
<Order />
<Inventory addFish={addFish} loadSampleFishes={loadSampleFishes} />
</div>
);
};
Fish
import React from "react";
import { formatPrice } from "../helpers";
export const Fish = ({ details, addToOrder, index }) => {
const isAvailable = details.status === "available";
const handleClick = () => {
addToOrder[index];
};
return (
<li className="menu-fish">
<img src={details.image} alt="" />
<h3 className="fish-names">
{details.name}
<span className="price">{formatPrice(details.price)}</span>
</h3>
<p>{details.desc}</p>
<button type="submit" disabled={!isAvailable} onClick={() => handleClick}>
{isAvailable ? "Add to order" : "Sold out!"}
</button>
</li>
);
};
TL;DR Solution
Now that I know what I am looking for, this was the issue: updating and merging an object using React useState hook.
I was missing to copy the previous state when updating order{} The rest was pretty much correct, so the bit of code with the improvement is:
const addOrder = (key) => {
const order = { ...state.order };
order[key] = order[key] + 1 || 1;
setState({
...state,
order: order,
});
};
This post (as well as the last answer on this one) really explains it well: https://stackoverflow.com/a/61243124/20615843
This is the relative bit in the React docs:https://reactjs.org/docs/hooks-reference.html#functional-updates
Apparently, and even better practice is using useReducer() as stated: https://stackoverflow.com/a/71093607/20615843