'Warning: setState(...): Cannot update during an existing state transition' 添加 react-native-router-flux Action() 之后
'Warning: setState(...): Cannot update during an existing state transition' after adding react-native-router-flux Action()
我有一个新的 React Native 应用程序 - 使用 redux、reduxThunk 和 react-native-router
我有一个 listItem 组件,当用户单击它时会触发 redux 操作 addBrandForUser()
。
class ListItem extends Component {
constructor(props) {
super(props);
this.onAddClick = this.onAddClick.bind(this);
}
onAddClick() {
let {addBrandForUser, brand} = this.props;
addBrandForUser({userId: null, brand: brand});
}
render() {
let {brand} = this.props;
return (
<Text style={styles.brandSelectItem}
onPress={this.onAddClick}>
{brand.title}
</Text>
)
}
}
点击会触发以下操作:
export const addBrandForUser = ({userId, brand}) => {
return (dispatch) => {
dispatch({
type: types.AddBrandToUser,
payload: {userId, brand}
});
dispatch({
type: types.SelectBrand,
payload: brand
});
//Actions.main();
}
}
然后在所有正确的减速器中对其进行评估。这一切都有效并且链接正确 - 所有调试器都达到了我期望的效果。
但是,我收到 Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount
异常....但前提是 Actions.main();
行未被注释。
我的路由器配置如下:
export default class RouterComponent extends Component {
render() {
return (
<Router>
<Scene key="main" component={Main} title="Butler" hideNavBar={true} initial/>
<Scene sceneStyle={{paddingTop: 60}} key="addBrand" component={AvailableBrands} title="Brands" hideNavBar={false}/>
</Router>
);
}
}
我注意到的一件事是,在调度 SelectBrand
操作后,减速器会正确更新,并且在导航回 Actions.main() 时相应地更新视图。然而,在调度 AddBrandToUser
操作后,reducer 获得了正确的有效负载并正确返回新对象,但视图没有相应更新。
需要更新的视图如下
class UserBrandList extends Component {
renderBrands(brand) {
return <BrandSelectItem brand={brand}/>;
}
render() {
let {flex1} = sharedStyles;
console.log('Render() ... Props.Brands: ');
console.log(this.props.brands);
return (
<ScrollView style={flex1}>
<ListView
enableEmptySections={true}
dataSource={this.props.brands}
renderRow={this.renderBrands.bind(this)}
/>
</ScrollView>
)
}
}
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
const mapStateToProps = state => {
console.log('MapStateToProps() .... state.userBrands: ');
console.log(state.userBrands);
return {
brands: ds.cloneWithRows(state.userBrands)
}
};
export default connect(mapStateToProps)(UserBrandList);
下面是上面 class 内部日志记录的一些输出。此序列在 listItem 上触发 addBrandForUser()
操作时开始。
看起来新数组正在正确缩减,但由于异常而恢复到原来的状态。
更新 1:
这是addBrandForUser()
命中的reducer。
export default (state = [], action) => {
switch (action.type) {
case types.RefreshUserBrands:
return action.payload;
case types.AddBrandToUser:
let newArray = [...state, action.payload.brand];
console.log('New Reduced Array: ' );
console.log(newArray);
return newArray;
default:
return state;
}
}
更新二:
Actions
是 react-native-router-flux 提供给我的 class:
import {Actions} from 'react-native-router-flux';
<Scene key="main" component={Main} title="Butler" hideNavBar={true} initial/>
Actions.main()
据我了解,使用密钥 main
路由到 scene
附加到Main
路由的组件如下:
export default class Main extends Component {
constructor(props) {
super(props);
this.openSideMenu = this.openSideMenu.bind(this);
this.closeSideMenu = this.closeSideMenu.bind(this);
}
closeSideMenu() {
this._drawer.close()
}
openSideMenu() {
this._drawer.open()
}
render() {
return (
<View style={styles.container}>
<ButlerHeader openDrawer={this.openSideMenu}/>
<Drawer
type="overlay"
ref={(ref) => {this._drawer = ref }}
content={<SideMenu closeDrawer={this.closeSideMenu} />}
openDrawerOffset={0.2}
drawerpanCloseMask={0.2}
closedDrawerOffset={-3}
tweenHandler={(ratio) => ({
main: { opacity:(2-ratio)/2 }
})}
/>
</View>
);
}
}
如果您向 Action.main
展示代码,将会有所帮助。但是我猜动作创建者在某种程度上扰乱了状态,或者有一些你没有提到的异步的东西在进行。
在这种情况下可以使用的一个小窍门是推迟你的 Actions.main
调用,方法是将它包装在一个:
setTimeout(() => Actions.main(), 0)
所以它会在下一个 tick 运行,在调度和状态更新完成后。
明确一点:这不是要走的路,这种错误通常是一种很好的代码味道,应该以不同的方式做一些事情。 ;)
想象一下顺序无关紧要……相反,您的 Actions.main()
首先被触发,然后您在尝试导航时尝试更改数据。
这可能是正在发生的事情。
虽然选择了答案。我只想 post 我找到的东西。
结论是Action.main()
最终会调用setState()
方法
也许 react-native-router-flux
应该像 this way 一样使用。
首先,Action.main()
将从 react-native-router-flux/blob/master/src/Actions.js L180 调用 this.callback
。
this[key] =
(props = {}) => {
assert(this.callback, 'Actions.callback is not defined!');
this.callback({ key, type, ...filterParam(props) });
};
this.callback
定义在 react-native-router-flux/blob/master/src/Router.js L137.
Actions.callback = (props) => {
const constAction = (props.type && ActionMap[props.type] ? ActionMap[props.type] : null);
if (this.props.dispatch) {
if (constAction) {
this.props.dispatch({ ...props, type: constAction });
} else {
this.props.dispatch(props);
}
}
return (constAction ? onNavigate({ ...props, type: constAction }) : onNavigate(props));
};
然后onNavigate
在L132中被renderNavigation(navigationState, onNavigate)
传递。并且 renderNavigation
在 L156 中传递给 <NavigationRootContainer>
。
NavigationRootContainer
由 NavigationExperimental
导入。
从react-native-experimental-navigation/blob/master/NavigationRootContainer.js,我们可以得到onNavigate
正好是handleNavigation
。在那里,你可以找到 setState
.
handleNavigation(action: Object): boolean {
const navState = this.props.reducer(this.state.navState, action);
if (navState === this.state.navState) {
return false;
}
this.setState({
// $FlowFixMe - navState is missing properties :(
navState,
});
if (this.props.persistenceKey) {
AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState));
}
return true;
}
我有一个新的 React Native 应用程序 - 使用 redux、reduxThunk 和 react-native-router
我有一个 listItem 组件,当用户单击它时会触发 redux 操作 addBrandForUser()
。
class ListItem extends Component {
constructor(props) {
super(props);
this.onAddClick = this.onAddClick.bind(this);
}
onAddClick() {
let {addBrandForUser, brand} = this.props;
addBrandForUser({userId: null, brand: brand});
}
render() {
let {brand} = this.props;
return (
<Text style={styles.brandSelectItem}
onPress={this.onAddClick}>
{brand.title}
</Text>
)
}
}
点击会触发以下操作:
export const addBrandForUser = ({userId, brand}) => {
return (dispatch) => {
dispatch({
type: types.AddBrandToUser,
payload: {userId, brand}
});
dispatch({
type: types.SelectBrand,
payload: brand
});
//Actions.main();
}
}
然后在所有正确的减速器中对其进行评估。这一切都有效并且链接正确 - 所有调试器都达到了我期望的效果。
但是,我收到 Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount
异常....但前提是 Actions.main();
行未被注释。
我的路由器配置如下:
export default class RouterComponent extends Component {
render() {
return (
<Router>
<Scene key="main" component={Main} title="Butler" hideNavBar={true} initial/>
<Scene sceneStyle={{paddingTop: 60}} key="addBrand" component={AvailableBrands} title="Brands" hideNavBar={false}/>
</Router>
);
}
}
我注意到的一件事是,在调度 SelectBrand
操作后,减速器会正确更新,并且在导航回 Actions.main() 时相应地更新视图。然而,在调度 AddBrandToUser
操作后,reducer 获得了正确的有效负载并正确返回新对象,但视图没有相应更新。
需要更新的视图如下
class UserBrandList extends Component {
renderBrands(brand) {
return <BrandSelectItem brand={brand}/>;
}
render() {
let {flex1} = sharedStyles;
console.log('Render() ... Props.Brands: ');
console.log(this.props.brands);
return (
<ScrollView style={flex1}>
<ListView
enableEmptySections={true}
dataSource={this.props.brands}
renderRow={this.renderBrands.bind(this)}
/>
</ScrollView>
)
}
}
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
const mapStateToProps = state => {
console.log('MapStateToProps() .... state.userBrands: ');
console.log(state.userBrands);
return {
brands: ds.cloneWithRows(state.userBrands)
}
};
export default connect(mapStateToProps)(UserBrandList);
下面是上面 class 内部日志记录的一些输出。此序列在 listItem 上触发 addBrandForUser()
操作时开始。
看起来新数组正在正确缩减,但由于异常而恢复到原来的状态。
更新 1:
这是addBrandForUser()
命中的reducer。
export default (state = [], action) => {
switch (action.type) {
case types.RefreshUserBrands:
return action.payload;
case types.AddBrandToUser:
let newArray = [...state, action.payload.brand];
console.log('New Reduced Array: ' );
console.log(newArray);
return newArray;
default:
return state;
}
}
更新二:
Actions
是 react-native-router-flux 提供给我的 class:
import {Actions} from 'react-native-router-flux';
<Scene key="main" component={Main} title="Butler" hideNavBar={true} initial/>
Actions.main()
据我了解,使用密钥 main
scene
附加到Main
路由的组件如下:
export default class Main extends Component {
constructor(props) {
super(props);
this.openSideMenu = this.openSideMenu.bind(this);
this.closeSideMenu = this.closeSideMenu.bind(this);
}
closeSideMenu() {
this._drawer.close()
}
openSideMenu() {
this._drawer.open()
}
render() {
return (
<View style={styles.container}>
<ButlerHeader openDrawer={this.openSideMenu}/>
<Drawer
type="overlay"
ref={(ref) => {this._drawer = ref }}
content={<SideMenu closeDrawer={this.closeSideMenu} />}
openDrawerOffset={0.2}
drawerpanCloseMask={0.2}
closedDrawerOffset={-3}
tweenHandler={(ratio) => ({
main: { opacity:(2-ratio)/2 }
})}
/>
</View>
);
}
}
如果您向 Action.main
展示代码,将会有所帮助。但是我猜动作创建者在某种程度上扰乱了状态,或者有一些你没有提到的异步的东西在进行。
在这种情况下可以使用的一个小窍门是推迟你的 Actions.main
调用,方法是将它包装在一个:
setTimeout(() => Actions.main(), 0)
所以它会在下一个 tick 运行,在调度和状态更新完成后。
明确一点:这不是要走的路,这种错误通常是一种很好的代码味道,应该以不同的方式做一些事情。 ;)
想象一下顺序无关紧要……相反,您的 Actions.main()
首先被触发,然后您在尝试导航时尝试更改数据。
这可能是正在发生的事情。
虽然选择了答案。我只想 post 我找到的东西。
结论是Action.main()
最终会调用setState()
方法
也许 react-native-router-flux
应该像 this way 一样使用。
首先,Action.main()
将从 react-native-router-flux/blob/master/src/Actions.js L180 调用 this.callback
。
this[key] =
(props = {}) => {
assert(this.callback, 'Actions.callback is not defined!');
this.callback({ key, type, ...filterParam(props) });
};
this.callback
定义在 react-native-router-flux/blob/master/src/Router.js L137.
Actions.callback = (props) => {
const constAction = (props.type && ActionMap[props.type] ? ActionMap[props.type] : null);
if (this.props.dispatch) {
if (constAction) {
this.props.dispatch({ ...props, type: constAction });
} else {
this.props.dispatch(props);
}
}
return (constAction ? onNavigate({ ...props, type: constAction }) : onNavigate(props));
};
然后onNavigate
在L132中被renderNavigation(navigationState, onNavigate)
传递。并且 renderNavigation
在 L156 中传递给 <NavigationRootContainer>
。
NavigationRootContainer
由 NavigationExperimental
导入。
从react-native-experimental-navigation/blob/master/NavigationRootContainer.js,我们可以得到onNavigate
正好是handleNavigation
。在那里,你可以找到 setState
.
handleNavigation(action: Object): boolean {
const navState = this.props.reducer(this.state.navState, action);
if (navState === this.state.navState) {
return false;
}
this.setState({
// $FlowFixMe - navState is missing properties :(
navState,
});
if (this.props.persistenceKey) {
AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState));
}
return true;
}