2

I currently have a component that does a history.push('/') after a few seconds. But I am getting warnings

index.js:1375 Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.

and also

index.js:1375 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.

I am fairly new to React, do I need do some sort of clean up?

Here is my component.

import React, {useState} from 'react'
import {UsePostOrPutFetch} from "../hooks/postHook";
import "./styles/ConfirmationChange.scss";
import moment from 'moment';


export default function ConfirmatonChange(props) {

  const [shouldFetch, setShouldFetch] = useState(false);
  const [data,loading,isError, errorMessage] = UsePostOrPutFetch("/send-email/", props.data.location.state.value,"POST", shouldFetch, setShouldFetch);
  const [countDown, setCountDown] = useState(5)

  let spinner = (
    <strong className="c-spinner" role="progressbar">
      Loading…
    </strong>
  );

    const changeView = () => 
    {
      if (countDown < 0) {
        props.data.history.push('/')
      } else {
        setTimeout(() => {
          setCountDown(countDown - 1)
         }
        , 1000)
      }
    }

    return (
        <div>
            <div className="o-container">
                  <article className="c-tile">
                  <div className="c-tile__content">
                    <div className="c-tile__body u-padding-all">

                      <button className = "c-btn c-btn--primary u-margin-right" onClick={props.data.history.goBack}>Edit</button>
                      <button className = "c-btn c-btn--secondary u-margin-right" disabled={loading} onClick={(e) => { setShouldFetch(true)}}>Send</button>
                      {!loading && data === 200 && !isError ? (
                      <div className="ConfirmationChange-success-send">
                       <hr hidden={!data === 200} className="c-divider" />
                        Email Sent succesfully
                        <p>You will be redirected shortly....{countDown}</p>
                          {changeView()}

                      </div>
                      ) : (loading && !isError ? spinner : 
                        <div className="ConfirmationChange-error-send">
                          <hr hidden={!isError} className="c-divider" />
                           {errorMessage}
                        </div>
                      )} 
                    </div>
                  </div>
                </article>
           </div>
        </div>
    )
}

And here is what my data fetch component look like

import { useState, useEffect } from "react";
import { adalApiFetch } from "../config/adal-config";

const UsePostOrPutFetch = (url, sendData, methodType, shouldFetch, setShouldSend) => {

  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const [errorMessage, setError] = useState("");

  useEffect(() => {
      const ac = new AbortController();
      if (shouldFetch) {
        const postOrPutData = async () => {
          try {
            const response = await adalApiFetch(fetch, url, 
              {
                signal: ac.signal,
                method: methodType,
                headers: {
                    'Content-Type': 'application/json'
                  },
                  body: JSON.stringify(sendData)
            });
            const json = await response.json();
            setData(await json);
            setLoading(true);
          } catch (err) {
            setIsError(true);
            setError(err.message);
          } finally {
            setShouldSend(false)
            setLoading(false);
          }
      };
      postOrPutData();
    }
      return () => { ac.abort(); };

    }, [shouldFetch, sendData, url, methodType, setShouldSend]);

    return [data, loading, isError, errorMessage];

  };

  export {UsePostOrPutFetch}

Any help would be greatly appreciated.

2 Answers 2

1

Check React Hooks - Check If A Component Is Mounted

The most common cause of this warning is when a user kicks off an asynchronous request, but leaves the page before it completes.

You'll need a componentIsMounted variable and useEffect and useRef hooks :

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

const changeView = () => {
  if (countDown < 0) {
    props.data.history.push("/");
  } else {
    setTimeout(() => {
      if (componentIsMounted.current) { // only update the state if the component is mounted
        setCountDown(countDown - 1);
      }
    }, 1000);
  }
};

You should do the same for data fetch component

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you this make complete sense, I've updated my code the warning has disappeared.
1

Yup, you have a timeOut that could potentially fire after the component has unmounted.

You need to add a useEffect that clears the timer onUnmount as follows

const timerRef = useRef();
useEffect(() => () => clearTimeout(timerRef.current), [])

const changeView = () => {
  if (countDown < 0) {
    props.data.history.push("/");
  } else {
    timerRef.current = setTimeout(() => {
      setCountDown(countDown - 1);
    }, 1000);
  }
};

1 Comment

I believe you don't need const in front of useEffect, you help is much appreciated. Manage to resolve my issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.