我在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 />;
}
}
我遇到如下错误。
警告:无法从组件的功能主体内部更新组件 不同的组件。
如何删除此警告?我在这里做错了什么?
答案 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):
答案 12 :(得分:0)
我正在同时更新多个子组件中的状态,这导致了意外行为。用 **software.amazon.awssdk.services...**
钩子替换 useState
对我有用。