I have a new React Native app - using redux, reduxThunk and react-native-router
I have a listItem component which triggers a redux action addBrandForUser()
when the user clicks it.
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>
)
}
}
The click triggers the following action:
export const addBrandForUser = ({userId, brand}) => {
return (dispatch) => {
dispatch({
type: types.AddBrandToUser,
payload: {userId, brand}
});
dispatch({
type: types.SelectBrand,
payload: brand
});
//Actions.main();
}
}
Which then gets evaluated in all the correct reducers. This all works and is linked up correctly - all the debuggers hit where I'm expecting them to.
However, I'm getting the 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
exception .... but only if the Actions.main();
line is uncommented.
My router configuration is as follows:
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>
);
}
}
One thing I'm noticing is that after the SelectBrand
action is dispatched, the reducers update correctly and the views are updated accordingly when navigating back to the Actions.main(). However, after dispatching the AddBrandToUser
action, the reducer gets the correct payload and is correctly returning the new object, but the views aren't updating accordingly.
The view which should be updated is as follows
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);
Below is some output from logging inside the above class. This sequence starts when the addBrandForUser()
action is triggered on the listItem.
It looks like the new array is being correctly reduced, but reverts back to its former self because of the exceptions.
Update 1:
Here is the reducer that addBrandForUser()
hits.
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;
}
}
Update 2:
Actions
is a class provided to me by react-native-router-flux:
import {Actions} from 'react-native-router-flux';
<Scene key="main" component={Main} title="Butler" hideNavBar={true} initial/>
Actions.main()
from what I understand, routes to the scene
with the key main
The component attached to the Main
route is as follows:
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>
);
}
}
Imagine the order doesn’t matter… and instead your Actions.main()
is triggered first, then you’re attempting to change data while it’s trying to navigate.
that’s likely what’s happening.