Search code examples
reactjsreact-hooksuse-effectreact-component-unmount

Trying to fix: Can't perform a React state update on an unmounted component


I have the following code:

const getAllSlidersUrl = "a url";
const adminID = "an admin id";
const adminlogintoken="a token";

const SliderContainer = () => {
    const [allSlides, setAllSlides] = useState([]);

    useEffect(
        () => {
            axios.post(
                getAllSlidersUrl,
                {
                    adminid: adminID,
                    token: adminlogintoken
                }
            )
                .then(
                    (response) => {
                        setAllSlides(response.data);
                    }
                )
                .catch(
                    (error) => {
                        alert(error);
                    }
                )

        }, []
    );

    return (
        //I have a button here
       //when I press button it executes the following:
       {
           allSlides.map(
                 item =>
                    <div key={item.slider_id} style={{ paddingTop: "10px", paddingBottom: "10px" }}>
                         <SliderComponent
                              adminId={adminID}
                              adminLoginToken={adminlogintoken}
                              sliderID={item.slider_id}
                              sliderImage={item.slider_image}
                              sliderEnText={item.slider_en_text}
                              sliderArText={item.slider_ar_text}
                              sliderEnButtonText={item.slider_en_button_text}
                              sliderArButtonText={item.slider_ar_button_text}
                              sliderEnButtonLink={item.slider_en_button_link}
                              sliderArButtonLink={item.slider_ar_button_link}
                              deleteSliderOnClick={deleteSliderHandler}
                              updateSliderToAllSlides={updatingSliderHandler}
                              />
                     </div>
                 )
       } 
   );
}

When I login to the page and press F12 (to give me the inspect tab),sometimes everything is fine and I get no warning but most of the times I get the following 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.

and whenever this warning is present and I press the button the application breaks and I get error: allSlides.map is not a function

I tried adding return () => {} at the end of the useEffect hook, I also tried changing useEffect to the following:

useEffect(
        () => {
            let isSubscribed = true;
            axios.post(
                getAllSlidersUrl,
                {
                    adminid: adminID,
                    token: adminlogintoken
                }
             )
             .then(
                (response) => {
                    if(isSubscribed){
                        setAllSlides(response.data);
                     }
                        
                }
             )
             .catch(
                 (error) => {
                     alert(error);
              }
           )
           return () => {isSubscribed = false}
    }, []
);

But in both cases where I tryied to resolve the error, it doesn't resolve it.

Note that for whatever reason (in all 3 cases) sometimes the web application doesn't break and everything works fine, but most of the times I do get the warning and then the error if I press the button.


Solution

  • Check the reponse data type if it is of array type or not because the error states that map is not a function that means allSlides is not an array.

    About the warning

    try using ref to cancel fetch like this:

    const cancelFetch = React.useRef(false);
    
    
    useEffect(
        () => {
            
            axios.post(
                getAllSlidersUrl,
                {
                    adminid: adminID,
                    token: adminlogintoken
                }
             )
             .then(
                (response) => {
                    if(cancelFetch.current){
    
                       return;
                        
                     }
                     setAllSlides(response.data);
                }
             )
             .catch(
                 (error) => {
                     alert(error);
              }
           )
           return () => {cancelFetch.current = true;}
    }, []
    );
    

    A better way to cancel is using abort controller but this can do too. If the warning is still there then maybe another component (parent) is getting unmounted.