react警告:无法从不同组件的函数体内部更新组件

IT技术 javascript reactjs redux
2021-04-14 12:51:51

我在 React 中使用 Redux 和 Class Components。在 Redux 商店中具有以下两种状态。

{ spinner: false, refresh: false }

在父组件中,我有一个调度函数来改变这个状态。

class App extends React.Component {
  reloadHandler = () => {
    console.log("[App] reloadComponent");

    this.props.onShowSpinner();
    this.props.onRefresh();
  };

  render() {
    return <Child reloadApp={this.reloadHandler} />;
  }
}

在子组件中,我试图重新加载父组件,如下所示。

class Child extends React.Component {
  static getDerivedStateFromProps(props, state) {
    if (somecondition) {
      // doing some redux store update
      props.reloadApp();
    }
  }

  render() {
    return <button />;
  }
}

我收到如下错误。

警告:无法从不同组件的函数体内部更新组件。

如何消除此警告?我在这里做错了什么?

6个回答

对我来说,我是在 React Hook 中分派到我的 redux 商店。我必须在 a 中调度以useEffect与 React 渲染周期正确同步:

export const useOrderbookSubscription = marketId => {
  const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
    variables: {
      marketId,
    },
  })

  const formattedData = useMemo(() => {
    // DISPATCHING HERE CAUSED THE WARNING
  }, [data])

  // DISPATCHING HERE CAUSED THE WARNING TOO

  // Note: Dispatching to the store has to be done in a useEffect so that React
  // can sync the update with the render cycle otherwise it causes the message:
  // `Warning: Cannot update a component from inside the function body of a different component.`
  useEffect(() => {
    orderbookStore.dispatch(setOrderbookData(formattedData))
  }, [formattedData])

  return { data: formattedData, error, loading }
}
这对我来说也是如此。我一一删除了我的调度事件,直到我找到了有问题的 fn。将它移动到 componentDidMount(或 useEffect)并且它起作用了!因为我是有条件地加载组件,所以这是一个问题。或者,您可以棘轮并将有条件地加载组件的标志(例如 useState 的 set 方法)放在 a 中setTimeout,它也可以工作...但您没有从我这里听到:x
2021-05-25 12:51:51
传奇。对我来说,我根本没有使用钩子就调度到 Redux 商店,我仍然会收到这个警告,但很奇怪(就像所有 React 的情况一样),只有一些调度,而不是全部,即使他们都更新了商店,导致重新渲染。我会说这是 v16.13.0 中引入的一个错误报告警告的错误,如下所述:github.com/facebook/react/issues/18178#issuecomment-601425520我当然不会从另一个组件更新一个组件,因为警告表明,只有相同的组件。无论如何,useEffect使警告消失。
2021-06-11 12:51:51

您似乎拥有最新版本的React@16.13.x. 您可以在此处找到有关它的更多详细信息指定您不应setState从其他组件中分离出另一个组件。

从文档:

支持在渲染过程中调用 setState,但仅限于同一组件。如果您在不同组件的渲染期间调用 setState,您现在将看到一条警告:
Warning: Cannot update a component from inside the function body of a different component.
此警告将帮助您查找由无意状态更改引起的应用程序错误。在极少数情况下,您故意希望更改另一个组件的状态作为渲染的结果,您可以将 setState 调用包装到 useEffect 中。


来到实际问题。

我认为getDerivedStateFromProps在子组件主体中不需要如果要触发绑定事件。然后你可以通过onClick子组件的调用它,因为我可以看到它是一个<button/>.

class Child extends React.Component {
  constructor(props){
    super(props);
    this.updateState = this.updateState.bind(this);
  }
  updateState() { // call this onClick to trigger the update
    if (somecondition) {
      // doing some redux store update
      this.props.reloadApp();
    }
  }

  render() {
    return <button onClick={this.updateState} />;
  }
}
@f0rfun 你可以在这里发布它。这肯定会对其他人有所帮助,因为这是新事物。
2021-05-29 12:51:51
这个答案帮助我解决了这个问题。我遇到了罕见的情况。将它包装在 useEffect 中可以消除错误。非常感谢。如果有人仍然不确定,请随时与我们联系。
2021-05-31 12:51:51
这在某种程度上解决了我的问题(甚至没有直接关系,但仍然如此)。用早晨的棉花眼onPress={navigation.navigate(...)}代替onPress={() => navigation.navigate(...)}
2021-05-31 12:51:51

如果您的代码在满足如下条件时调用父组件中的函数:

const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
    const { data, loading, error } = useQuery(QUERY);

    if (data && data.users.length === 0) {
        return handleNoUsersLoaded();
    }

    return (
        <div>
            <p>Users are loaded.</p>
        </div>
    );
};

尝试将条件包装在 a 中useEffect

const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
    const { data, loading, error } = useQuery(QUERY);

    useEffect(() => {
        if (data && data.users.length === 0) {
            return handleNoUsersLoaded();
        }
    }, [data, handleNoUsersLoaded]);

    return (
        <div>
            <p>Users are loaded.</p>
        </div>
    );
};
谢谢你,我有代码可以响应 props 的变化,但我忘了把它放在useEffect钩子里。感谢你的帮助!
2021-05-27 12:51:51
将条件包装在 useEffect 中对我有用。获取数据后,我收到此代码的错误: if (data) dispatch(actionFunction({ data }); if (!data) return <Loading />; 并修复了它: useEffect(() => { if (data) dispatch(actionFunction({ data }); }, [data, dispatch]); if (!data) return <Loading />;
2021-06-04 12:51:51

相同的错误但不同的场景

tl;博士包装状态更新setTimeout修复它。

这种情况导致了 IMO 是有效用例的问题。

const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
  setSomeState(someNewValue);
}).current;
return (
  <SomeComponent onSomeUpdate={doUpdate} />
);

使固定

const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
  setTimeout(() => {
   setSomeState(someNewValue);
  }, 0);
}).current;
return (
  <SomeComponent onSomeUpdate={doUpdate} />
);

@AbhilashKishore setTimout 将函数安排在当前事件循环之后发生,在当前事件上下文之外,该上下文具有否则会触发控制台警告的标志。
2021-05-27 12:51:51
wrapping state update in setTimeout fixes it 但为什么?
2021-06-06 12:51:51
虽然我不知道它为什么会修复,但你的解决方案对我来说很好。谢谢杰森!
2021-06-17 12:51:51

就我而言,我错过了箭头功能 ()=>{}

代替 onDismiss={()=>{/*do something*/}}

我有它 onDismiss={/*do something*/}

我在两个地方遇到了这个错误,修复了两个地方都可以帮助箭头函数。
2021-06-01 12:51:51