Salah Eddine Makdour
Salah Eddine Makdour

Reputation: 1042

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

I made a login form so when someone logs in successfully, the server sends the user info (name, email, id) and the some page states change accordingly (like route, user object), but whenever I log in everything works well but i get an error in the console saying:

index.js:1 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 the componentWillUnmount method.
    in Login (at App.js:226)

It really confuses me bcs the app still works well and idk why i'm getting this error.

app.js's render():

  render() {

    if (this.state.route === 'login') {
      return <Login loadUser={this.loadUser} routeChange={this.routeChange} /> //this is line 226 at app.js

    } else if (this.state.route === 'register') {
      return <Register loadUser={this.loadUser} routeChange={this.routeChange} />

    } else {
      return (
        <div>
          {/* The rest of the app's home components */}
        </div>
      )
    }

login.js:

    class Login extends Component {
    constructor(props) {
        super(props);
        this.state = {
            email: "",
            password: "",
            loading: false
        }
    }

    handleChange = (e) => {
        this.setState({ [e.target.name]: e.target.value })

    }

    handleClick = () => {

        this.setState({ loading: true })
        fetch('myApi/login', {
            method: 'post',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                email: this.state.email,
                password: this.state.password
            })
        })
            .then(response => response.json())
            .then(user => {
                if (user.id) {
                    this.props.routeChange('home');

                    this.props.loadUser(user);
                    //localStorage.setItem('user', JSON.stringify(user));
                    this.setState({ loading: false })
                }
            })
    }

    render() {
        return (
            <div className="container">
                <div className="row">
                    <div className="col-sm-9 col-md-7 col-lg-5 mx-auto">
                        <div className="card card-signin my-5">
                            <div className="card-body">
                                <h5 className="card-title text-center">Sign In</h5>
                                <form onSubmit={(e) => e.preventDefault()} className="form-signin">
                                    <div className="form-label-group">
                                        <input onChange={this.handleChange} name="email" type="email" id="inputEmail" className="form-control" placeholder="Email address" autoFocus />
                                        <label htmlFor="inputEmail">Email address</label>
                                    </div>
                                    <div className="form-label-group">
                                        <input onChange={this.handleChange} name="password" type="password" id="inputPassword" className="form-control" placeholder="Password" />
                                        <label htmlFor="inputPassword">Password</label>
                                    </div>
                                    <div className="custom-control custom-checkbox mb-3">
                                        <input type="checkbox" className="custom-control-input" id="customCheck1" />
                                        <label className="custom-control-label" htmlFor="customCheck1">Remember password</label>
                                    </div>
                                    <button onClick={this.handleClick} className="btn btn-lg btn-primary btn-block text-uppercase">Sign in</button>
                                    <hr className="my-4" />
                                    <h6 onClick={() => this.props.routeChange('register')} style={{ textAlign: 'center', cursor: 'pointer' }}>Register instead</h6>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

Upvotes: 1

Views: 286

Answers (2)

Drew Reese
Drew Reese

Reputation: 203587

The issue is this thenable condition, when you setState after the route changed. Remember state updates are asynchronous and occur before each new render cycle.

if (user.id) {
  this.props.routeChange('home');
  this.props.loadUser(user);
  //localStorage.setItem('user', JSON.stringify(user));
  this.setState({ loading: false }) // <-- error since component unmounted!!
}

Removing the loading state back to false should address warning:

if (user.id) {
  this.props.routeChange('home');
  this.props.loadUser(user);
}

EDIT To really fix this correctly, move the route change to a lifecycle function so you can correctly set loading state back to false if login didn't work or user object doesn't have id property.

handleClick = () => {
  this.setState({ loading: true })
  fetch('myApi/login', {
    method: 'post',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: this.state.email,
      password: this.state.password
    })
  })
    .then(response => response.json())
    .then(user => {
      if (user.id) {
        this.props.loadUser(user);
        this.setState({ user })
      }
    })
    .finally(() => this.setState({ loading: false })); // run this no matter what
}

componentDidUpdate(prevProps, prevState) {
  // check user state condition, if met, change route
  if (/* user condition met */) {
    this.props.routeChange('home');
  }
}

Upvotes: 2

RenegadeRanger
RenegadeRanger

Reputation: 94

I assume the problem is actually in your loadUser function. Could you share it? Are you changing the page in the loadUser function?

componentDidMount() {
   this.isMounted = true;
}

componentWillUnmount() {
   this.isMounted = false;
}

handleClick = () => {
fetch('myApi/login', {
    method: 'post',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        email: this.state.email,
        password: this.state.password
    })
})
    .then(response => response.json())
    .then(user => {
        if (user.id) {
            if (this.isMounted) this.setState({ this.state..., loading: false });
            if (this.isMounted) this.props.loadUser(user);
            if (this.isMounted) this.props.routeChange('home');
        }
    })
}

Upvotes: 1

Related Questions