呈现不同组件警告时无法更新组件

IT技术 javascript reactjs redux
2021-04-07 23:58:32

我在react中收到此警告:

index.js:1 Warning: Cannot update a component (`ConnectFunction`) 
while rendering a different component (`Register`). To locate the 
bad setState() call inside `Register` 

我去了堆栈跟踪中指示的位置并删除了所有设置状态,但警告仍然存在。这可能发生在 redux dispatch 中吗?

我的代码:

注册.js

class Register extends Component {
  render() {
    if( this.props.registerStatus === SUCCESS) { 
      // Reset register status to allow return to register page
      this.props.dispatch( resetRegisterStatus())  # THIS IS THE LINE THAT CAUSES THE ERROR ACCORDING TO THE STACK TRACE
      return <Redirect push to = {HOME}/>
    }
    return (
      <div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
        <RegistrationForm/>
      </div>
    );
  }
}

function mapStateToProps( state ) {
  return {
    registerStatus: state.userReducer.registerStatus
  }
}

export default connect ( mapStateToProps ) ( Register );

在 register.js 调用的 registerForm 组件中触发警告的函数

handleSubmit = async () => {
    if( this.isValidForm() ) { 
      const details = {
        "username": this.state.username,
        "password": this.state.password,
        "email": this.state.email,
        "clearance": this.state.clearance
      }
      await this.props.dispatch( register(details) )
      if( this.props.registerStatus !== SUCCESS && this.mounted ) {
        this.setState( {errorMsg: this.props.registerError})
        this.handleShowError()
      }
    }
    else {
      if( this.mounted ) {
        this.setState( {errorMsg: "Error - registration credentials are invalid!"} )
        this.handleShowError()
      }
    }
  }

堆栈跟踪:

堆栈跟踪

6个回答

这个警告是从 React V16.3.0 开始引入的。

如果您正在使用功能组件,您可以将 setState 调用包装到 useEffect 中。

不起作用的代码:

const HomePage = (props) => {
    
  props.setAuthenticated(true);

  const handleChange = (e) => {
    props.setSearchTerm(e.target.value.toLowerCase());
  };

  return (
    <div key={props.restInfo.storeId} className="container-fluid">
      <ProductList searchResults={props.searchResults} />
    </div>
  );
};

现在您可以将其更改为:

const HomePage = (props) => {
  // trigger on component mount
  useEffect(() => {
    props.setAuthenticated(true);
  }, []);

  const handleChange = (e) => {
    props.setSearchTerm(e.target.value.toLowerCase());
  };

  return (
    <div key={props.restInfo.storeId} className="container-fluid">
      <ProductList searchResults={props.searchResults} />
    </div>
  );
};
这为我修好了。现在我想弄清楚为什么。有什么帮助吗?
2021-05-27 23:58:32
@sawyerrken这个链接可能会回答部分原因,但总的来说,当我尝试解决错误时,这个问题继续让我感到困惑。
2021-05-29 23:58:32
就我而言,我固定移动dispatchuseEffect
2021-06-02 23:58:32
你刚刚拯救了我的早晨!
2021-06-03 23:58:32
你应该第二个参数添加[]useEffect,如果你想这只是一次运行(不是每次状态得到更新)。
2021-06-10 23:58:32

来到这里是因为我刚刚遇到了这个问题,在我意识到我做错了什么之前我花了一些时间进行挖掘 - 我只是没有注意我是如何编写我的功能组件的。

我想我会在这里留下答案以防万一有人来看,他们犯了和我一样的简单错误。

我是这样做的:

const LiveMatches = (props: LiveMatchesProps) => {
  const {
    dateMatches,
    draftingConfig,
    sportId,
    getDateMatches,
  } = props;

  if (!dateMatches) {
    const date = new Date();
    getDateMatches({ sportId, date });
  };

  return (<div>{component stuff here..}</div>);
};

useEffect在发送我的 redux 调用之前,我刚刚忘记使用getDateMatches()

太愚蠢了,我在其他组件中一直在做的事情,哈哈。

所以它应该是:

const LiveMatches = (props: LiveMatchesProps) => {
  const {
    dateMatches,
    draftingConfig,
    sportId,
    getDateMatches,
  } = props;

  useEffect(() => {
    if (!dateMatches) {
      const date = new Date();
      getDateMatches({ sportId, date });
    }
  }, [dateMatches, getDateMatches, sportId]);

  return (<div>{component stuff here..}</div>);
};

简单而愚蠢的错误,但花了一段时间才意识到它,所以希望这能帮助其他人解决这个问题。

天啊。我刚刚看到您的回答,并立即理解了错误。谢谢!帮了我很多
2021-05-23 23:58:32
@Plumpie 因为 react 在“周期”中工作,所以它会跟踪渲染的内容以及何时更新和渲染新组件。它通过使用钩子或生命周期方法来做到这一点。如果这个函数不在 useEffect 钩子内,那么组件将尝试在每次重新渲染时调用它,并且这也会更新组件 - 与 react 想要的顺序不符。将代码移动到一个钩子中,允许反应以正确的顺序更新组件,以便它可以随着事情的变化而跟踪。
2021-05-26 23:58:32
为什么将您的代码放入 useEffect 会消除此警告?
2021-06-10 23:58:32
同样,我正在通过 Context 更新功能,看到这个问题,谢谢,它也帮助了我!
2021-06-11 23:58:32

我通过从注册组件渲染方法中删除到 componentwillunmount 方法的分派来解决这个问题。这是因为我希望这个逻辑在重定向到登录页面之前发生。一般来说,最好的做法是将所有逻辑都放在 render 方法之外,这样我的代码以前写得不好。希望这对未来的其他人有所帮助:)

我重构的注册组件:

class Register extends Component {

  componentWillUnmount() {
    // Reset register status to allow return to register page
    if ( this.props.registerStatus !== "" ) this.props.dispatch( resetRegisterStatus() )
  }

  render() {
    if( this.props.registerStatus === SUCCESS ) { 
      return <Redirect push to = {LOGIN}/>
    }
    return (
      <div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
        <RegistrationForm/>
      </div>
    );
  }
}

你也可以做一些解构,例如:const {someDispatch} = props;,然后在任何你想要的地方使用你的调度。
2021-06-09 23:58:32

如果useEffect不能在您的情况下使用或者错误不是因为 Redux

我曾经setTimeout将两个useState变量之一重定向到回调队列。

我有一个父组件和一个子组件,useState每个组件都有变量。解决方案是useState使用setTimeout以下方法包装变量

setTimeout(() => SetFilterData(data), 0);

下面的例子

父组件

import ExpenseFilter from '../ExpensesFilter'
    
function ExpensesView(props) {
    
    const [filterData, SetFilterData] = useState('')
    
    const GetFilterData = (data) => {
       // SetFilterData(data);

       //*****WRAP useState VARIABLE INSIDE setTimeout WITH 0 TIME AS BELOW.*****
       setTimeout(() => SetFilterData(data), 0);
    
    }
    
    const filteredArray = props.expense.filter(expenseFiltered => 
      expenseFiltered.dateSpent.getFullYear().toString() === filterData);
    
    
    return (
    <Window>
      <div>
        <ExpenseFilter FilterYear = {GetFilterData}></ExpenseFilter>

子组件

const ExpensesFilter = (props) => {
    
    const [filterYear, SetFilterYear] = useState('2022')
    
    const FilterYearListener = (event) => {
        event.preventDefault()
        SetFilterYear(event.target.value)
    }
    
    props.FilterYear(filterYear)
    
    return (
setImmediate 将是一个不错的选择。
2021-05-22 23:58:32
这是有效的,因为它setTimeout()是一个阻塞函数,迫使setFilterData函数不再是异步的。这不是状态挂钩的工作方式,基本上是一种可能导致意外行为的混搭。
2021-06-05 23:58:32

TL; 博士; 就我而言,我为修复警告所做的工作是从更改useStateuseRef

react_devtools_backend.js:2574 Warning: Cannot update a component (`Index`) while rendering a different component (`Router.Consumer`). To locate the bad setState() call inside `Router.Consumer`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
    at Route (http://localhost:3000/main.bundle.js:126692:29)
    at Index (http://localhost:3000/main.bundle.js:144246:25)
    at Switch (http://localhost:3000/main.bundle.js:126894:29)
    at Suspense
    at App
    at AuthProvider (http://localhost:3000/main.bundle.js:144525:23)
    at ErrorBoundary (http://localhost:3000/main.bundle.js:21030:87)
    at Router (http://localhost:3000/main.bundle.js:126327:30)
    at BrowserRouter (http://localhost:3000/main.bundle.js:125948:35)
    at QueryClientProvider (http://localhost:3000/main.bundle.js:124450:21)

我所做的事情的上下文的完整代码(从行更改为// OLD:它们上方的行)。然而这并不重要,只需尝试从 更改useStateuseRef!!

import { HOME_PATH, LOGIN_PATH } from '@/constants';
import { NotFoundComponent } from '@/routes';
import React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
import { useAccess } from '@/access';
import { useAuthContext } from '@/contexts/AuthContext';
import { AccessLevel } from '@/models';

type Props = RouteProps & {
  component: Exclude<RouteProps['component'], undefined>;
  requireAccess: AccessLevel | undefined;
};

export const Index: React.FC<Props> = (props) => {
  const { component: Component, requireAccess, ...rest } = props;

  const { isLoading, isAuth } = useAuthContext();
  const access = useAccess();
  const mounted = React.useRef(false);
  // OLD: const [mounted, setMounted] = React.useState(false);

  return (
    <Route
      {...rest}
      render={(props) => {
        // If in indentifying authentication state as the page initially loads, render a blank page
        if (!mounted.current && isLoading) return null;
        // OLD: if (!mounted && isLoading) return null;

        // 1. Check Authentication is one step
        if (!isAuth && window.location.pathname !== LOGIN_PATH)
          return <Redirect to={LOGIN_PATH} />;
        if (isAuth && window.location.pathname === LOGIN_PATH)
          return <Redirect to={HOME_PATH} />;

        // 2. Authorization is another
        if (requireAccess && !access[requireAccess])
          return <NotFoundComponent />;

        mounted.current = true;
        // OLD: setMounted(true);
        return <Component {...props} />;
      }}
    />
  );
};

export default Index;