简体   繁体   中英

Fix "Can't perform a React state update on an unmounted component" error

The code below gives a console error when I try to push to a new page in my web application:

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. in PasswordResetPage (created by Context.Consumer)

You will have noticed in the code that is commented out that I've tried to use useEffect as this is what most stackoverflow answers points to. However, the error is still present. How can I use history.push in this context and get rid of the error?

import React, { useState /* , useEffect */ } from 'react'
import useForm from 'react-hook-form'
import Logo from '../assets/images/logo-icon-orange.png'
import TextInput from '../components/TextInput'
import Footer from '../components/Footer'
import AnimatedButton from '../components/AnimatedButton'
import logger from '../logger'
import { sleep } from '../utils/promise'
import PasswordResetSchema from './validation/PasswordResetSchema'

const PasswordResetPage = ({ history }) => {
  const [isSubmitting, setSubmitting] = useState(false)
  // const [isDone, setDone] = useState(false)
  const { register, handleSubmit, errors } = useForm({
    validationSchema: PasswordResetSchema,
  })

  const onSubmit = async data => {
    setSubmitting(true)
    await sleep(2000)
    logger.info('form data', data)
    // setDone(true)
    history.push('/confirmation')
  }

  // useEffect(() => {
  //   if (isDone) {
  //     history.push('/confirmation')
  //   }
  // })


  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <div className="text-center mb-4">
        <img className="mb-4" src={Logo} alt="Striver" width={72} />
        <h1 className="h3 mb-3 font-weight-normal">Password reset</h1>
        <p>Enter your email address below and we will send you instructions on how you can reset your password.</p>
      </div>

      <div className="form-label-group">
        <TextInput type="email" id="email" register={register} label="Email address" errors={errors} />
      </div>

      <AnimatedButton actionTitle="Next" isSubmitting={isSubmitting} />
      <Footer />
    </form>
  )
}

export default PasswordResetPage

See if that works for you:

Code SandBox with working example:

https://codesandbox.io/s/reactrouteranswerso-817bp

const onSubmit = async data => {
    setSubmitting(true)
    await sleep(2000)
    logger.info('form data', data)
    setDone(true)
  }

  useEffect(() => {
    if (isDone) {
      history.push('/confirmation')
    }
  },[isDone]);

Working code on CodeSandbox:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

import "./styles.css";

function App() {
  const [user, setUser] = useState(null);

  function doLogin() {
    console.log("Inside do login...");
    setUser("someUserID");
  }

  function doLogout() {
    console.log("Inside do logout...");
    setUser(null);
  }

  return (
    <Router>
      <AllRoutes user={user} doLogin={doLogin} doLogout={doLogout} />
    </Router>
  );
}

function AllRoutes(props) {
  console.log("Rendergin AllRoutes...");
  // console.log(props);
  return (
    <Switch>
      <Route exact path="/" component={Component1} />
      <Route exact path="/comp2" component={Component2} />
    </Switch>
  );
}

function Component1(props) {
  const [isDone, setIsDone] = useState(false);

  useEffect(() => {
    if (isDone) {
      props.history.push("/comp2");
    }
  }, [isDone, props.history]);

  return (
    <React.Fragment>
      <div>Component1</div>
      <button onClick={() => setIsDone(true)}>Submit</button>
    </React.Fragment>
  );
}

function Component2(props) {
  const [goBack, setGoBack] = useState(false);

  useEffect(() => {
    if (goBack) {
      props.history.push("/");
    }
  }, [goBack, props.history]);

  return (
    <React.Fragment>
      <div>Component2</div>
      <button onClick={() => setGoBack(true)}>Go Back</button>
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

I believe await sleep(2000) is a replacement for actual API call.
API calls as well as history.push are side effects thus should be places in useEffect .
Try this:

const PasswordResetPage = ({ history }) => {
  const [formData, setFormData] = useState(null);

  // ...

  const onSubmit = async data => {
    setFormData(data);
  }

  useEffect(() => {
    logger.info('form data', formData);
    sleep(2000).then(() => history.push('/confirmation'););
  }, [formData]);

  // ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM