3
votes

I get this warning when i use my fetch post method how can I cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.With my Post methods.

Warning: 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.

import React from "react";
import { useHistory } from "react-router-dom";
import { UPLOAD_PRESET, CLOUD_NAME, SERVER_API } from "../../config";

const uploadImage = async (file) => {
  const url = `https://api.cloudinary.com/v1_1/${CLOUD_NAME}/upload`;
  const formData = new FormData();
  formData.append("file", file);
  formData.append("upload_preset", UPLOAD_PRESET);

  const res = await fetch(url, {
    method: "POST",
    body: formData,
  });

  if (!res.ok) {
    throw new Error(`Can't upload image. ${res.status}`);
  }

  const data = await res.json();
  return await data.eager[0].secure_url;
};

const createAlbum = async (data) => {
  const res = await fetch(`${SERVER_API}/api/v1/albums`, {
    method: "POST",
    body: JSON.stringify(data),
    headers: {
      "Content-Type": "application/json",
    },
  });

  if (!res.ok) {
    throw new Error(`An error has occurred: ${res.status}`);
  }

  const json = await res.json();
  return json.data._id;
};

const Form = ({ file, loading, setError, album, color, children }) => {
  let history = useHistory();

  const clearError = () => setError("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    clearError();
    try {
      if (!file) {
        throw new Error("Please select a file to add.");
      }

      if (!album.trim("") || !color.trim()) {
        throw new Error("Please enter all the field values.");
      }

      loading(true);

      const fileUrl = await uploadImage(file);

      const data = {
        name: album,
        bckImgUrl: fileUrl,
        color: color,
      };

      const albumId = await createAlbum(data);

      history.push(`/albums/${albumId}`);
    } catch (error) {
      setError(error.message);
    } finally {
      loading(false);
    }
  };
  return <form onSubmit={handleSubmit}>{children}</form>;
};

export default Form;
2
that is a warning... not an errorassembler
i see but how to fix itKuba Kluzniak
you are showing only the functions to call the api, not how, or when you are calling themassembler
if u scroll down code its in handleSubmit functionKuba Kluzniak

2 Answers

1
votes

I agree with Ramesh on using a ref. I figured I'd show how it could be extracted out into a custom hook.

function useHasUnmountedRef() {
  const hasUnmountedRef = useRef(false);
  useEffect(() => {
    return () => {
      hasUnmountedRef.current = true;
    }
  }, []);
  return hasUnmountedRef;
}

function Form() {

  const hasUnmountedRef = useHasUnmountedRef();

  const handleSubmit = async () => {

    await asyncStuff();

    if (hasUnmountedRef.current) {
      // escape early because component has unmounted
      return;
    }

    // Thanks to the guard clause above, you can guarantee at this
    // point that your component is still mounted. You can perform
    // state updates without generating a React warning. 
    //
    // If you do another "await" however, you will need to check
    // again. Everytime you await something async, there is a chance
    // that the component could have unmounted while waiting for the
    // async stuff to complete.

  };

  return (
    <form onSubmit={handleSubmit} />
  );
}

export default Form;
1
votes

You can use React.useRef for that:

In your component:

const hasUnmounted = React.useRef(false);

React.useEffect(() => {

 // maybe you have your side effects here, so you can use the boolean to avoid 
 // state updates when the component is unmounted

 if(!hasUnmounted.current) {
  // do state update 
 }
 return () => {
  hasUnmounted.current = true;
 }
}, [])

React.useRef is suitable to solve this issue as it is very different from changing the state of your component, this is kind of like instance variables in class components, changing this won't trigger a rerender.