React function keeps refreshing page causing huge memory leaks

Viewed 54

I am building a website with React. Currently I have created function that renders elements and does PUT fetch call to API I created in Node.js. Here is how it looks like:

import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import Alert from "../elements/Alert";
import Axios from "axios";

export default function UpdateCourse() {
  const id = window.location.href.split("?")[1];
  console.log(id);

  const [dishName, setdishName] = useState("");
  const [category, setcategory] = useState("");
  const [author, setauthor] = useState("");
  const [ingredients, setingredients] = useState([]);
  const [cookingTime, setcookingTime] = useState("");
  const [sourceUrl, setsourceUrl] = useState("");
  const [imageUrl, setimageUrl] = useState("");
  const [isPublished, setisPublished] = useState("true");
  const [price, setprice] = useState("");
  const [tags, settags] = useState([]);
  const [alert, setAlert] = useState("");
  const history = useHistory();

  const url = `http://localhost:1234/api/courses/find/${id}`;

  useEffect(async () => {
    const result = await Axios.get(url);
    setdishName(result.data.dishName);
    setcategory(result.data.category);
    setauthor(result.data.author);
    setingredients(result.data.ingredients);
    setcookingTime(result.data.cookingTime);
    setsourceUrl(result.data.sourceUrl);
    setimageUrl(result.data.imageUrl);
    setisPublished(result.data.isPublished);
    setprice(result.data.price);
    settags(result.data.tags);
    console.log(`Nie powinno`);
  });

  async function update() {
    let item = {
      dishName,
      category,
      author,
      ingredients,
      cookingTime,
      sourceUrl,
      imageUrl,
      isPublished,
      price,
      tags,
    };

    console.log(item);
    console.log(JSON.stringify(item));

    const result = await fetch(`http://localhost:1234/api/courses/${id}`, {
      method: "PUT",
      body: JSON.stringify(item),
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });

    console.log(result);
    if (result.status !== 200) {
      return setAlert(result.status + " " + result.statusText);
    }

    history.push("/");
    window.location.reload();
  }

  const handleIngredientsChange = (event, index) => {
    console.log("id: " + event.target.id + "\nvalue: " + event.target.value);
    const shallowCopy = [...ingredients];

    shallowCopy[index] = {
      ...shallowCopy[index],
      [event.target.id]: event.target.value,
    };

    setingredients(shallowCopy);
  };

  return (
    <div>
      {alert !== "" && <Alert alert={alert}></Alert>}
      <div>
        <p>Dishname</p>
        <input
          autoFocus="autofocus"
          required="required"
          type="text"
          className="form-control"
          placeholder={dishName}
          onChange={e => setdishName(e.target.value)}
        />
      </div>
      <br />

      <div>
        <p>Category</p>
        <input
          required="required"
          type="text"
          className="form-control"
          placeholder={category}
          onChange={e => setcategory(e.target.value)}
        />
      </div>
      <br />

      <div>
        <p>Author</p>
        <input
          type="text"
          onChange={e => setauthor(e.target.value)}
          className="form-control"
          required="required"
          placeholder={author}
        />
      </div>
      <br />

      <p>Ingredients</p>
      {ingredients.map(({ quantity, unit, description }, index) => {
        console.log("tick");
        return (
          <div style={{ display: "flex", justifyContent: "space-between" }}>
            <h5>Quantity </h5>
            <br />
            <input
              id="quantity"
              type="text"
              onChange={event => handleIngredientsChange(event, index)}
              className="form-control"
              required="required"
              placeholder={quantity}
            />
            <h5> Unit </h5>
            <div>
              <input
                id="unit"
                type="text"
                onChange={event => handleIngredientsChange(event, index)}
                className="form-control"
                required="required"
                placeholder={unit}
              />
            </div>
            <h5> Description </h5>

            <input
              id="description"
              type="text"
              onChange={event => handleIngredientsChange(event, index)}
              className="form-control"
              required="required"
              placeholder={description}
            />
          </div>
        );
      })}

      <br />
      <div>
        <p>Cooking time</p>
        <input
          type="text"
          onChange={e => setcookingTime(e.target.value)}
          className="form-control"
          required="required"
          placeholder={cookingTime}
        />
      </div>
      <br />

      <div>
        <p>Source url</p>
        <input
          type="text"
          onChange={e => setsourceUrl(e.target.value)}
          className="form-control"
          required="required"
          placeholder={sourceUrl}
        />
      </div>
      <br />

      <div>
        <p>Image url</p>
        <input
          type="text"
          onChange={e => setimageUrl(e.target.value)}
          className="form-control"
          required="required"
          placeholder={imageUrl}
        />
      </div>
      <br />

      <div>
        <p>Published</p>
        <input
          type="text"
          onChange={e => setisPublished(e.target.value)}
          className="form-control"
          required="required"
          placeholder={"default: true"}
        />
      </div>
      <br />

      <div>
        <p>Price</p>
        <input
          type="text"
          onChange={e => setprice(e.target.value)}
          className="form-control"
          required="required"
          placeholder={price}
        />
      </div>
      <br />

      <div>
        <p>Tags</p>
        <input
          type="text"
          onChange={e => settags(e.target.value)}
          className="form-control"
          required="required"
          placeholder={tags}
        />
      </div>
      <br />

      <button onClick={update}>Submit</button>
    </div>
  );
}

However while page does load and everything looks correct after few seconds my comupter starts making a lot of noise and when I do console.log to check what it is doing I get this: https://i.stack.imgur.com/5Sg3q.png

Could you please tell me what am I doing wrong? I did use useEffect function accordingly to what I read about this issue. Sadly nothing I did solved my problem.

EDIT: Here is what JSON object looks like in MongoDB:

{
    "isPublished": true,
    "tags": [
        "pizza"
    ],
    "_id": "60ae108ddfb18463c046a5ba",
    "dishName": "Pizza with Cauliflower Crust",
    "category": "pizza",
    "ingredients": [
        {
            "_id": "60a4cfa48c20aa5c18517606",
            "quantity": 1,
            "unit": "",
            "description": "medium head cauliflower cut into florets"
        },
        {
            "_id": "60a4cfa48c20aa5c18517607",
            "quantity": 1,
            "unit": "",
            "description": "egg"
        },
        {
            "_id": "60a4cfa48c20aa5c18517608",
            "quantity": 0.5,
            "unit": "cup",
            "description": "mozzarella shredded"
        },
        {
            "_id": "60a4cfa48c20aa5c18517609",
            "quantity": 1,
            "unit": "tsp",
            "description": "oregano or italian seasoning blend"
        },
        {
            "_id": "60a4cfa48c20aa5c1851760a",
            "quantity": null,
            "unit": "",
            "description": "Salt and pepper to taste"
        },
        {
            "_id": "60a4cfa48c20aa5c1851760b",
            "quantity": 1,
            "unit": "cup",
            "description": "chicken cooked and shredded"
        },
        {
            "_id": "60a4cfa48c20aa5c1851760c",
            "quantity": 0.5,
            "unit": "cup",
            "description": "barbecue sauce"
        },
        {
            "_id": "60a4cfa48c20aa5c1851760d",
            "quantity": 0.75,
            "unit": "cup",
            "description": "mozzarella shredded"
        },
        {
            "_id": "60a4cfa48c20aa5c1851760e",
            "quantity": null,
            "unit": "",
            "description": "Red onion to taste thinly sliced"
        },
        {
            "_id": "60a4cfa48c20aa5c1851760f",
            "quantity": null,
            "unit": "",
            "description": "Fresh cilantro to taste"
        }
    ],
    "cookingTime": 60,
    "sourceUrl": "https://www.closetcooking.com/cauliflower-pizza-crust-with-bbq/",
    "imageUrl": "https://www.closetcooking.com/wp-content/uploads/2013/02/BBQ-Chicken-Pizza-with-Cauliflower-Crust-500-4699.jpg",
    "price": 29.99,
    "date": "2021-05-26T09:10:37.620Z",
    "__v": 0
}
2 Answers

Setting a state in a useEffect that does not have empty dependency array will result in a loop. you should add an empty array in your useEffect, witch basically act as the previous componentDidMount() and therefore will be triggered only once.

 useEffect(async () => {
    const result = await Axios.get(url);
    setdishName(result.data.dishName);
    setcategory(result.data.category);
    setauthor(result.data.author);
    setingredients(result.data.ingredients);
    setcookingTime(result.data.cookingTime);
    setsourceUrl(result.data.sourceUrl);
    setimageUrl(result.data.imageUrl);
    setisPublished(result.data.isPublished);
    setprice(result.data.price);
    settags(result.data.tags);
    console.log(`Nie powinno`);
  }, []); <=== Here

This code can be a root cause depending on data

const handleIngredientsChange = (event, index) => {
    console.log("id: " + event.target.id + "\nvalue: " + event.target.value);
    const shallowCopy = [...ingredients];

    shallowCopy[index] = {
      ...shallowCopy[index],
      [event.target.id]: event.target.value,
    };

    setingredients(shallowCopy);
  };

It updates global variable ingredients and if event.target.id differs from call to call the ingredients[index] will be ever growing.

Notice you use constant IDs like <input id="quantity" ...> but it's an inside of a ingredients.map(...) so multiple of such inputs per page. But the id is supposed to unique per page. So maybe browser auto-assign the id property for input/event. Notice that attribute and property are different things in HTML DOM.

Log the event.target.id and make sure they are unique.

I would advise passing a constant instead of event.target.id like:

const handleIngredientsChange = (name, value, index) => {
  ...
}

<input
  type="text"
  onChange={event => handleIngredientsChange('quantity', event.target.value,  index)}
  className="form-control"
  required="required"
  placeholder={quantity}
            />

Notice I've removed ID as you really don't want to use ID inside of a cycle or you should use composite one like id={'quantity'+index}.

Related