1

Good day, tell me where the cant is please. The task is to display a material-ui alert message that comes from the server.

onSubmit={(values) => {
          sendRegistrationCode(values.email).then((result) => {
            if (result.data.message) {
              setError({
                type: result.data.message.type,
                message: result.data.message.message,
              });
            }
          });
        }}

as here you can see a promise comes and I want to write its result into a state hook of this kind

const [error, setError] = useState();

and then output in this way

{error && <AlertsUi status={error.type} message={error.message} />}

but I run into an 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. Thank you, I would be grateful for an explanation of how to overcome this.

P.S- If it matters, then I assign the value of the promise in the onSubmit of the form from Formik.

MAKEDA
  • 61
  • 7

1 Answers1

0

You can use the typical approach with isMounted/isUnmounted flag variable to avoid changing its state after the component has been unmounted. Something like the following:

function Component() {
    const isUnmounted = React.useRef(false);
    const [error, setError] = React.useState("");

    React.useEffect(() => () => (isUnmounted.current = true), []);

    return <form onSubmit={(e)=>{
        e.preventDefault();
        sendRegistrationCode('email').then((result) => {
            if (result.data.message) {
                !isUnmounted.current && setError({
                    type: result.data.message.type,
                    message: result.data.message.message,
                });
            }
        });
    }}></form>;
}

Of course, this old well-known trick actually doesn't cancel async routines or unsubscribe from anything, it just ignores their results.

And just for reference, as an experimental approach, you can do the following trick with a custom callback hook. It provides a wrapper around cancelable promises. Note: Unmounting the component while fetching will automatically cause aborting of the network request. (minimal live demo)

import React, { useState } from "react";
import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
import cpAxios from "cp-axios";

export default function TestComponent(props) {
  const [text, setText] = useState("");
  const [error, setError] = useState("");

  const sendRegistrationCode = (email) => {
    return cpAxios(props.url);
  };

  const submit = useAsyncCallback(
    function* (e) {
      e.preventDefault();

      try {
        const response = yield sendRegistrationCode("email").timeout(
          props.timeout
        );

        if (response.data.message) {
          setError({
            type: response.data.message.type,
            message: response.data.message.message
          });
          return;
        }
        setText(response); //response OK
      } catch (err) {
        CanceledError.rethrow(err, E_REASON_UNMOUNTED);
        setError({ type: "fetch", message: err.message });
      }
    },
    { cancelPrevious: true }
  );

  return (
    <div className="component">
      <div className="caption">useAsyncEffect demo:</div>
      <div>
        {error ? (
          <span style={{ color: "red" }}>{JSON.stringify(error)}</span>
        ) : (
          <span>{JSON.stringify(text)}</span>
        )}
      </div>
      <form action={props.url} onSubmit={submit}>
        <input type="submit" value="Send form" />
      </form>
    </div>
  );
}
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7