40

I'm getting this error:

warning.js:33 Warning: Can't call setState (or forceUpdate) 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.

But I'm not using a componentWillUnMount method.

I'm using a HOC to make sure the user is authenticated before accessing their /account route.

Here's the Route:

<StyleRoute props={this.props} path="/account" component= 
{RequireAuth(Account)} />

where RequireAuth is the HOC. Here's the HOC:

 import { withRouter } from 'react-router';

export default function RequireAuth(Component) {

  return class AuthenticatedComponent extends React.Component {

    componentWillMount() {
      this.checkAuth();
    }

    checkAuth() {
      if ( ! this.props.isAuthenticated) {
        this.props.history.push(`/`);
      }
    }

    render() {
      return this.props.isAuthenticated
        ? <Component { ...this.props } />
        : null;
    }

  }

  return withRouter(AuthenticatedComponent);
}

The code works as intended, but I'm getting that error when /account is rendered. As you notice, nowhere in my direct code is there an componentWillUnMount method. I'm really at a loss for why this warning keeps popping up and any info would help.


Update 5/23/18:

To get rid of the error and still have props pass down, I did two thing:

1) I opted for a having two higher order functions in parent App component instead of using the HOC. One higher order function is for passing props and the other is to check authentication. I was having trouble passing any props other than the browser history, hence the renderProps function below.

renderProps = (Component, props) => {
  return (
      <Component {...props} />
    );
}

checkAuth = (Component, props) => {
    if (props.isAuthenticated) {
        return <Component {...props} />
    }
    if (!props.isAuthenticated) {
        return <Redirect to='/' />
    }
}

2) To use these, I had to user render in my Route, as opposed to component.

//I could pass props doing this, sending them through the above functions
<Route exact path="/sitter-dashboard" render={ () => this.checkAuth(SitterDashboard, this.props) } />
<Route exact path={"/account/user"} render={() => this.renderProps(User, this.props)} />

//I couldn't pass props doing this
<Route {...this.props} exact path="/messages" component={Messages} />

Here's the documentation on router vs component as a Route render method: https://reacttraining.com/react-router/web/api/Route/route-render-methods

Also, here's a good explanation on Stack Overflow

Finally, I used this code from the React Router 4 documentation as a template for what I did above. I'm sure the below is cleaner, but I'm still learning and what I did makes a bit more sense to me.

const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
  {...rest}
  render={props =>
  fakeAuth.isAuthenticated ? (
       <Component {...props} />
      ) : (
        <Redirect
          to={{
            pathname: "/login",
            state: { from: props.location }
          }}
        />
      )
    }
  />
);
Cameron
  • 567
  • 1
  • 5
  • 13
  • 1
    Can you try the same by changing ComponentWillMount to ComponentDidMount? – Rohith Murali Apr 25 '18 at 19:03
  • I did but it throws an error since it needs to know if they are authenticated before the component is rendered. – Cameron Apr 25 '18 at 19:47
  • Ok, in that case you can remove the history.push from there and set a flag to show that its unauthenticated and return in render() in unauthenticated cases. The histoty.push will be trying to unmount components which are not mounted hence the error. – Rohith Murali Apr 26 '18 at 00:58
  • Thanks for the help. I ended up using render instead of component, as mentioned in the React Router 4 documentation, and the error went away. – Cameron May 10 '18 at 18:51
  • What do you mean with that? Could you please write a short exemple of your code? I don't find any point of the doc talking about this – Pibo May 21 '18 at 11:32
  • Pibo, done. I still don't know why I was getting the initial error, but it seems to be cleared up now. – Cameron May 23 '18 at 17:00
  • Hi @CameronTharp I believe that I am doing the exact same thing as you, and I do receive the same warning. Here is my take on the routes: https://gist.github.com/ahlusar1989/d46edb1f73987838b358e05649624639. I welcome feedback on if this is a similar pattern as your original implementation. I am particularly wondering why you need render over component props in the route? Is this specific to React Router V4? – ahlusar1989 May 28 '18 at 14:04

2 Answers2

14

I had the same error time ago and it was generated by a component which was using ref tag, and there was some manual manipulation.

A good practice to see these kind of errors is drawing your app flow and see when your are calling setState.

Another thing I would change if I were you is componentDidMount instead of componentWillMount to check some data. Take into account fb deprecated this functionality.

This lifecycle was previously named componentWillMount. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.

Reactjs component documentation

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Lucas Milotich
  • 370
  • 3
  • 15
8

I had a similar problem, but I did figure out the reason behind the same, so here is the snippet of code where I was encountering this err.

Warning: Can't call setState (or forceUpdate) 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.

Cause:

   this.setState({ showLoader: true });
    const { username } = this.state;
    const URL = `https://api.github.com/users/${username}`;
    try {
      const { data } = await axios(URL);
      this.props.apiData(data);
      this.props.history.push("profile");
    } catch (e) {
      console.error(e);
    }
    this.setState({ showLoader: false });

As you can see in the code-snippet, I was doing

this.props.history.push("profile");

before setting the state.

this.setState({ showLoader: false });

And then err seems to be legit in this case as I was redirecting to a different component and then setting the state on the component I was earlier.

Solution:

By placing

this.setState({ showLoader: false });

above the this.props.history.push("profile"); solved the problem.

I hope this helps.

Divyanshu Rawat
  • 4,421
  • 2
  • 37
  • 53