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

Tam*_*jid 71 javascript reactjs redux

我在反应中收到此警告:

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

我去了堆栈跟踪中指示的位置并删除了所有设置状态,但警告仍然存在。这可能发生在 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 );
Run Code Online (Sandbox Code Playgroud)

在 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()
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

堆栈跟踪:

堆栈跟踪

小智 89

这个警告是从 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>
  );
};
Run Code Online (Sandbox Code Playgroud)

现在您可以将其更改为:

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

  • 如果你希望它只运行一次(而不是每次状态更新时),你应该将第二个参数``[]````添加到``useEffect```。 (15认同)
  • 你能详细说明一下**为什么会这样**吗? (11认同)
  • 就我而言,我通过在“useEffect”中移动“dispatch”来修复 (7认同)
  • @sawyerrken [此链接](https://reactjs.org/blog/2020/02/26/react-v16.13.0.html#new-warnings)可能会回答部分原因,但总的来说,这个问题仍然令人困惑当我尝试解决错误时。 (3认同)
  • 这为我解决了这个问题。现在我正试图找出原因。有什么帮助吗? (2认同)
  • @HamidShoja,你的解决方案**对我有用**,我在“useEffect”之外使用“dispatch”,它导致了问题。 (2认同)

shi*_*z27 69

请仔细阅读错误消息。我的指向具有错误 setState 的 SignIn 组件。我有一个 onPress 不是 Arrow function

事情是这样的:

onPress={navigation.navigate("Home", { screen: "HomeScreen" })}
Run Code Online (Sandbox Code Playgroud)

我把它改成这样:

onPress={() => navigation.navigate("Home", { screen: "HomeScreen" }) }
Run Code Online (Sandbox Code Playgroud)

我的错误消息是:

ForwardRef(BaseNavigationContainer)警告:渲染不同组件 ( ) 时无法更新组件( SignIn)。要找到内部错误的 setState() 调用 ,请遵循 SignIn 中https://reactjs.org/link/setstate-in-renderSignIn中所述的堆栈跟踪 (位于 SignInScreen.tsx:20)

  • 这就是我在编码时感到半睡半醒时发生的事情。谢谢你救了我! (7认同)

Spi*_*iff 23

我认为这很重要。是出自这个帖子@Red-Baron

@machineghost:我认为您误解了该消息所警告的内容。

将回调传递给子级以更新父级中的状态并没有什么问题。一直都这样就好了

问题是当一个组件在另一个组件中排队更新,而第一个组件正在渲染时。

换句话说,不要这样做:

function SomeChildComponent(props) {
    props.updateSomething();
    return <div />
}
Run Code Online (Sandbox Code Playgroud)

但这很好:

function SomeChildComponent(props) {
    // or make a callback click handler and call it in there
    return <button onClick={props.updateSomething}>Click Me</button>
}
Run Code Online (Sandbox Code Playgroud)

而且,正如 Dan 多次指出的那样,在渲染时在同一组件中排队更新也很好:

function SomeChildComponent(props) {
  const [number, setNumber] = useState(0);

  if(props.someValue > 10 && number < 5) {
    // queue an update while rendering, equivalent to getDerivedStateFromProps
    setNumber(42);
  }

  return <div>{number}</div>
}
Run Code Online (Sandbox Code Playgroud)


小智 18

如果useEffect无法在您的情况下使用或者错误不是由于 Redux 造成的

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

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

setTimeout(() => SetFilterData(data), 0);
Run Code Online (Sandbox Code Playgroud)

下面的例子

父组件

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>
Run Code Online (Sandbox Code Playgroud)

子组件

const ExpensesFilter = (props) => {
    
    const [filterYear, SetFilterYear] = useState('2022')
    
    const FilterYearListener = (event) => {
        event.preventDefault()
        SetFilterYear(event.target.value)
    }
    
    props.FilterYear(filterYear)
    
    return (
Run Code Online (Sandbox Code Playgroud)

  • 这是有效的,因为“setTimeout()”是一个阻塞函数,强制“setFilterData”函数不再是异步的。这不是状态钩子的工作原理,基本上是一个可能导致意外行为的组合。 (3认同)
  • `setImmediate` 将是一个不错的选择。 (2认同)
  • @DannyApostolov 根据 MDN setImmediate 是非标准的,不应该在面向网络的生产站点上使用。 (2认同)

Bre*_*ast 16

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

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

我是这样做的:

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>);
};
Run Code Online (Sandbox Code Playgroud)

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>);
};
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么将代码放入 useEffect 会消除此警告? (5认同)
  • @Plumpie 因为 React 在“循环”中工作,所以它会跟踪渲染的内容以及何时更新和渲染新组件。它通过使用钩子或生命周期方法来实现这一点。如果此函数不在 useEffect 挂钩内,则组件将尝试在每次重新渲染时调用它,并且还会更新组件 - 与 React 想要的顺序不符。将代码移动到钩子中,允许反应以正确的顺序更新组件,以便它可以跟踪事情的变化。 (5认同)
  • 天啊。我刚刚看到你的回答并立即明白了错误。谢谢你!对我帮助很大 (2认同)

Tam*_*jid 12

我通过从 register components render 方法中删除 dispatch 到 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>
    );
  }
}

Run Code Online (Sandbox Code Playgroud)


小智 6

使用 React 和 Material UI (MUI),我将代码更改为:

<IconButton onClick={setOpenDeleteDialog(false)}>
        <Close />
      </IconButton>
Run Code Online (Sandbox Code Playgroud)

到:

<IconButton onClick={() => setOpenDeleteDialog(false)}>
        <Close />
      </IconButton>
Run Code Online (Sandbox Code Playgroud)

简单修复

  • 这里只是一个“我也是”,我在渲染期间执行处理程序而不是分配它。就我而言,我正在做这样的事情: &lt;button onClick={props.onCancel()} /&gt; 而不是 &lt;button onClick={props.onCancel} /&gt; 。 (2认同)

小智 5

如果您使用React Navigation并且正在使用setParamssetOptions您必须将这些放在componentDidMount()类组件的方法内部或useEffects()功能组件的钩子中。