0

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>
  );
};

calling the function from $r

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

  • If you're a beginner in react and prefer hooks I'd suggest pick a course that explains hooks like [john smilga's react beginner course](https://www.youtube.com/watch?v=iZhV0bILFb0&list=PLQC0rHfztJACDCd9BcLB_4dpa2rlhL5nr) or the react beta docs. Trying to convert class components to functional at this stage will only confuse you more. – narravabrion Nov 27 '22 at 16:46
  • Thanks for the suggestion - I think so far I have a good grasp of the concepts, I am not sure more theory could help. This seems to be mostly an issue with passing data in the wrong way. Do you have any suggestion on this specific case? – Jim Halpert Nov 27 '22 at 16:58
  • You are updating objects the wrong way. [Read this on how to properly update objects](https://beta.reactjs.org/learn/updating-objects-in-state). Also I don't get how you are calling `addToOrder`. – narravabrion Nov 27 '22 at 17:12
  • @narravabrion can you be more specific with both remarks? How am I updating objects in the wrong way, and what is not clear about calling addToOrder()? – Jim Halpert Nov 27 '22 at 17:24
  • I am making a copy of state `const order = { ...state.order };` I am doing the logic `order[key] = order[key] + 1 || 1;` I am triggering a re-render via setState, so not causing mutations `setState({ order: order, });` What is wrong with it? – Jim Halpert Nov 27 '22 at 17:27

2 Answers2

0

The onClick={() => handleClick} should be

either with parenthesis at the end to call it

onClick={() => handleClick()} 

or better yet, pass it directly as the callback method

onClick={handleClick}
Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
  • Thank you! I did try that, and originally I had it as callback, but that doesn't seem to be the issue. – Jim Halpert Nov 27 '22 at 16:39
  • Another odd behaviour I have noticed is that if I inspect App from the react dev tool, I can see fish: {} and order: {} - when I populate the fish state with some hardcoded examples the order: {} is not visible anymore, and I am not sure I understand why. – Jim Halpert Nov 27 '22 at 16:41
0

First welcome to react world Jim Halpert! Hope you are enjoying your journey.

Their a couple of issues I have found in your example.

1)In the Fish.jsx click handler you need to pass the index while calling the actual function.

2)You have to bind the index as props, from the parent JSX file.

3)Since you have order and fishes in the same object you will have to copy the previous data as well as shown in line 57 of App.jsx

I have tweaked your example a bit, have a look at it below:

import React from "react";
import { useState } from "react";
import { Fish } from "./Fish";

const App = () => {
  const [state, setState] = useState({
    fishes: {
      "salmon" : {
        "image":"https://via.placeholder.com/20x20",
        "name":"salmon",
        "description":"test",
        "status":"available"
      }
    },
    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 = (fish) => {
    
    const order = { ...state.order };
    order[fish] = order[fish.key] + 1 ?? 1;
    console.log("Order >>>>>>>>>>>> "+JSON.stringify(order));
    setState({
      ...state,
      order: order,
    });
  };

  return (
    <div className="catch-of-the-day">
      <div className="menu">
        
        <ul className="fishes">
           {Object.keys(state.fishes).map((key) => {
            return (
             <Fish
                index={key}
                details={state.fishes[key]}
                addToOrder={addToOrder}
              ></Fish>
            );
          })}
        </ul>
      </div>
      
    </div>
  );
};

export default App;

import React from "react";
export const Fish = ({ details, addToOrder, index }) => {
  const isAvailable = details.status === "available";

  const handleClick = (index) => {
    addToOrder(index);
  };

  return (
   <li className="menu-fish">
      <img src={details.image} alt="" />
      <h3 className="fish-names">
        {details.name}
        <span className="price">{details.price}</span>
      </h3>
      <p>{details.desc}</p>
      <button type="submit" disabled={!isAvailable} onClick={() => handleClick(index)}>
        {isAvailable ? "Add to order" : "Sold out!"}
      </button>
      
    </li>
  );
};
Sam Kaz
  • 432
  • 3
  • 12
  • Hey @Sam Kaz thank you so much, appreciated! I am not sure I am following some of the things I am seeing. Just to make sure we are on the same page: we need to update order{} I have tried your suggestions but that doesn't seem to work, maybe I am missing something. - what is key here? `order[fish] = order[fish.key] + 1 ?? 1;` – Jim Halpert Nov 27 '22 at 18:44
  • Definitely getting closer. I now get the object populated with `order : {[object Object]: 1}` I sa you used stringify in your example, but that can't work. What I need is fish:1, fish:2, etc. Any ideas? – Jim Halpert Nov 27 '22 at 20:06
  • My bad, I forgot to `() => handleClick(index)` in the child component. Definitely upvoting your answer, you pretty much nailed it, thank you so much, sir :) One last question: why do I need to copy again the state? A link to the docs will do as well, thanks :) `setState({ ...state, order: order, });` – Jim Halpert Nov 27 '22 at 20:14
  • 1
    (and just realised I am too n00b to upvote, but I left a feedback) – Jim Halpert Nov 27 '22 at 20:23
  • I am glad I could help, yes setState overrides the whole object even if you are modifying parts of the object you will still have to copy the previous data – Sam Kaz Nov 27 '22 at 20:24
  • I did a good read up on that, thank you very much, this was something very important to learn and that I will definitely not forget! PS. I have updated the initial post with some docs and results from my searches on this specific topic, and apparently useReducer would be a better way to handle this case, just FYI – Jim Halpert Nov 28 '22 at 08:34