反应警告:无法从其他组件的函数体内更新组件

时间:2020-03-04 13:05:25

标签: javascript reactjs redux

我在React中将Redux与类组件一起使用。在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 />;
  }
}

我遇到如下错误。

警告:无法从组件的功能主体内部更新组件 不同的组件。

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

13 个答案:

答案 0 :(得分:26)

对我来说,我正在使用React Hook调度到我的redux商店。我必须派遣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 }
}

答案 1 :(得分:17)

似乎您拥有React@16.13.x的最新版本。您可以找到关于它的更多详细信息here。已指定您不应setState从其他组件中提取另一个组件。

来自文档:

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


谈到实际问题。

我认为子组件主体中不需要getDerivedStateFromProps。如果要触发绑定事件。然后,您可以通过Child组件的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} />;
  }
}

答案 2 :(得分:12)

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

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>
    );
};

尝试将条件包装在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>
    );
};

答案 3 :(得分:2)

升级React和Native之后,我遇到了同样的问题,我只是通过将props.navigation.setOptions放入useEffect来解决了这个问题。如果有人面临与我相同的问题,我只想建议他改变您的状态或在useEffect中进行任何更改

答案 4 :(得分:2)

我使用 redux 使用 react-native-swiper 保存 swiperIndex 时遇到此错误

通过将changeSwiperIndex放入超时来修复它

答案 5 :(得分:1)

注释了一些代码行,但此问题可解决:)出现此警告的原因是,您正在其他类中同步调用reloadApp,将调用推迟到componentDidMount()

import React from "react";

export default 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();
    // }
  }

  componentDidMount(props) {
    if (props) {
      props.reloadApp();
    }
  }

  render() {
    return <h1>This is a child.</h1>;
  }
}

答案 6 :(得分:1)

相同错误,但情况不同

tldr; 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} />
);

答案 7 :(得分:1)

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

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

我的名字是onDismiss={/*do something*/}

答案 8 :(得分:1)

在 react native 中,如果您使用热重载自己更改代码中的状态,我发现我收到此错误,但使用按钮更改状态会使错误消失。

但是将我的 useEffect 内容包装在一个 :

setTimeout(() => {
    //....
}, 0);

即使在热重载时也能工作,但我不想无缘无故地使用愚蠢的 setTimeout,所以我删除了它并发现通过代码更改它效果很好!

答案 9 :(得分:0)

如果要从子组件自动调用作为道具传递的某些函数,则最好的位置是使用类组件的componentDidMount生命周期方法,或者如果使用功能组件的useEffect挂钩,因为此时已完全创建并安装了组件。

答案 10 :(得分:0)

我遇到了一个问题,即编写带有几个文本框的过滤器组件,允许用户限制另一个组件中列表中的项目。我正在Redux状态下跟踪过滤的项目。该解决方案本质上是@Rajnikant的解决方案;带有一些示例代码。

由于以下原因,我收到了警告。请注意渲染功能中的props.setFilteredItems

import {setFilteredItems} from './myActions';
const myFilters = props => {
  const [nameFilter, setNameFilter] = useState('');
  const [cityFilter, setCityFilter] = useState('');

  const filterName = record => record.name.startsWith(nameFilter);
  const filterCity = record => record.city.startsWith(cityFilter);

  const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
  props.setFilteredItems(selectedRecords); //  <-- Danger! Updates Redux during a render!

  return <div>
    <input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
    <input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
  </div>
};

const mapStateToProps = state => ({
  records: state.stuff.items,
  filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);

当我用React 16.12.0运行此代码时,我在浏览器控制台中收到此线程主题中列出的警告。基于堆栈跟踪,令人讨厌的行是我在render函数中对props.setFilteredItems的调用。因此,我将过滤器调用和状态更改简单地封装在useEffect中,如下所示。

import {setFilteredItems} from './myActions';
const myFilters = props => {
  const [nameFilter, setNameFilter] = useState('');
  const [cityFilter, setCityFilter] = useState('');

  useEffect(() => {
    const filterName = record => record.name.startsWith(nameFilter);
    const filterCity = record => record.city.startsWith(cityFilter);

    const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
    props.setFilteredItems(selectedRecords); //  <-- OK now; effect runs outside of render.
  }, [nameFilter, cityFilter]);

  return <div>
    <input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
    <input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
  </div>
};

const mapStateToProps = state => ({
  records: state.stuff.items,
  filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);

当我第一次添加useEffect时,由于每次调用useEffect都会导致状态改变,所以我使栈顶失效。我必须添加一个跳过效果数组,以便该效果仅在过滤器字段本身发生更改时才能运行。

答案 11 :(得分:0)

我建议您看下面的视频。正如OP的问题中的警告所暗示的那样,由于另一个兄弟(孩子1)向父母的回调,父母(父母)试图过早地更新一个孩子(孩子2)的属性存在更改检测问题。对我来说,孩子2过早/错误地调用了传递给Parent回调中的内容,从而引发了警告。

请注意,此通信工作流程仅是一种选择。我个人更喜欢通过共享的Redux存储在组件之间交换和更新数据。但是,有时它是过大的。该视频提供了一种干净的替代方法,其中孩子是“笨蛋”,只能通过道具和回调进行交谈。

还请注意,如果在子1的“事件”(如单击按钮)上调用了回调,则该回调将起作用,因为届时子代已被更新。无需超时,useEffects等。在这种狭窄的情况下,UseState就足够了。

这是链接(感谢Masoud):

https://www.youtube.com/watch?v=Qf68sssXPtM

答案 12 :(得分:0)

我正在同时更新多个子组件中的状态,这导致了意外行为。用 **software.amazon.awssdk.services...** 钩子替换 useState 对我有用。