Kex
Kex

Reputation: 8629

Cannot update a component (`App`) error in component

I have the following component:

import React, { useState, useEffect, useContext } from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import DashboardContext from '../../contexts/DashboardContext';
import authorizeWorker from '../../workers/authorize-worker';

/**
 * A protected route that calls the authorize API and redirects to login
 * on fail
 * @param {Function} component The component to redirect to on success 
 */
const ProtectedRoute  = ({ component }) => {
    const [isAuthenticated, setIsAuthenticated] = useState(null);
    const dashboardContext = useContext(DashboardContext);
    dashboardContext.setIsDashboard(true);
    const Component = component;

    useEffect(() => {
        authorizeWorker({})
            .then(() => setIsAuthenticated(true))
            .catch(() => setIsAuthenticated(false));
    }, []);

    if (isAuthenticated === true) {
        return <Component />;
    }

    if (isAuthenticated === false) {
        return <Redirect to="/login" />;
    }

    return null;
}

ProtectedRoute.propTypes = {
    component: PropTypes.func
};

export default ProtectedRoute;

I use this for my router e.g.

<ProtectedRoute path="/projects" component={Projects} />

I am recently seeing a warning in the console: Warning: Cannot update a component (App) while rendering a different component (ProtectedRoute). To locate the bad setState() call insideProtectedRoute, follow the stack trace as described. Why am I seeing this error and how can I fix it?

Upvotes: 2

Views: 2204

Answers (2)

Dennis Vash
Dennis Vash

Reputation: 53994

The error is because on initial render phase, you render the component with setIsDashboard(true);, usually, you want to do it on mount (useEffect with empty dep array).

There is an initial render phase, then mount phase, see component's lifecycle diagram.

Be sure that setIsDashboard is persistent, meaning it is created by React API (like useState).

Or its memoized with useMemo/useCallback, or you will get inifite loop because on every render a new instance of setIsDashboard will be created and the dep array ([setIsDashboard]) will cause another execution.

const ProtectedRoute = ({ component }) => {
  const { setIsDashboard } = useContext(DashboardContext);

  // Make sure `setIsDashboard` persistant
  useEffect(() => {
    setIsDashboard(true);
  }, [setIsDashboard]);

  ...
};

Upvotes: 3

mikeb
mikeb

Reputation: 11309

The error is because you can't set state when you are rendering:

dashboardContext.setIsDashboard(true); is probably the problem.

You don't post your stack trace or line numbers so it's hard to tell exactly what the issue is:

https://stackoverflow.com/help/minimal-reproducible-example

Upvotes: 0

Related Questions