1
votes

I can't update the state when I am calling a second API inside a react functional component. The first API call is inside useEffect, and the second API call is done when the user clicks the button. When the second API call is done react throws this error "Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function." And the state is not updating, I want to set the state after the second API call. How to fix this?

My code:

const AddNewProduct = () => {
  const [productName, setProductName] = useState("");
  const [originalPrice, setOriginalPrice] = useState("");
  const [newPrice, setNewPrice] = useState("");
  const [category, setCategory] = useState("");
  const [description, setDescription] = useState("");
  const [categoriesArray, setCategoriesArray] = useState([]);
  const [isLogin, setIsLogin] = useState([]);
  const [id, setId] = useState("");

  useEffect(() => {
    const getCategoriesData = async () => {
      const Data = await fetchCategoriesApi();
      setIsLogin(Data.data.login);
      setCategoriesArray(Data.data.data);
      console.log(Data);
    };
    getCategoriesData();
  }, []);

  const handleCategoryClick = (id) => {
    setCategory(id);
    console.log(id);
  };

  const handleNextClick = async () => {
    const postApi = "https://fliqapp.xyz/api/seller/products";

    try {
      const post = await axios
        .post(
          postApi,
          {
            product_name: productName,
            product_desc: description,
            product_price: originalPrice,
            product_cat: category,
          },
          {
            headers: {
              Authorization: `Bearer ${localStorage.getItem("token")}`,
            },
          }
        )
        .then((response) => {
          setId(response.data.data.product_id);
          console.log(id);
          console.log(response);
        });
    } catch (error) {
      return error;
    }

    console.log("clicked");
  };

  return (
    <>
      <div className={styles.container}>
        <div className={styles.blank}></div>
        <input
          type="text"
          className={styles.input_field}
          placeholder="Product name*"
          onChange={(e) => setProductName(e.target.value)}
        />
        <input
          type="text"
          className={styles.input_field}
          placeholder="original price*"
          onChange={(e) => setOriginalPrice(e.target.value)}
        />
        <input
          type="text"
          className={styles.input_field}
          placeholder="new price"
          onChange={(e) => setNewPrice(e.target.value)}
        />
        <select
          name="parent category"
          id="parentcategory"
          className={styles.dropdown}
          defaultValue={"DEFAULT"}
          onChange={(e) => handleCategoryClick(e.target.value)}
        >
          <option value="DEFAULT" disabled>
            select category
          </option>
          {isLogin &&
            categoriesArray.map((item, index) => (
              <option value={item.id} key={index}>
                {item.cat_name}
              </option>
            ))}
        </select>
        <textarea
          type="textarea"
          className={styles.input_field}
          placeholder="Description"
          rows="4"
          onChange={(e) => setDescription(e.target.value)}
        />
        <Link
          to={{
            pathname: `/add_image/${id}`,
          }}
          className={styles.btn}
          onClick={handleNextClick}
          disabled
        >
          Next
        </Link>

        <div className={styles.header}>
          <h1 className={styles.heading_normal}>Add new product</h1>
        </div>
      </div>
    </>
  );
};
1

1 Answers

1
votes

You need to change your Link to Button and manually navigate to other route because id used in route /add_image/${id} is coming from second Api call.

Reason : because when you click on Link it will fire axios request and change route of your app, thus current component is unmounted and new route component is mounted, after this happens your axios response comeback and try to setState on unmounted component.

// import
import { useHistory } from 'react-router-dom';


// inside component
const history = useHistory();


// click handler
const handleNextClick = async () => {
   // ...axiosrequest
  .then((response) => {
      setId(response.data.data.product_id); // may be not needed now
      const id = response.data.data.product_id;
      history.push(`/add_image/${id}`);
  }
}

// button
<button
  className={styles.btn}
  onClick={handleNextClick}  
>
  Next
</button>

In this way you change route only once after you get proper response from server and based on response ID you update your route.

For better user experience you can show loading meanwhile you perform axios ajax request.

if any doubt please comment.