I have a global button that is located in the root navigator and I also have a custom modal component that has its own reducer and actions. I am calling a toggle function inside the global button to toggle the modal, but when I compare the speed of the toggling on a modal that uses an ordinary state, it is much faster than with redux state. Why is this so?
Modal:
<Modal
visible={this.props.showCoinModal}
animationType="fade"
transparent={true}
onRequestClose={() => console.log('closed')}
>
Mapping:
const mapStateToProps = state => ({
showCoinModal: state.coinModal.showCoinModal
})
const mapDispatchToProps = dispatch => {
return {
onToggleCoinModal: () => dispatch(toggleCoinModal()),
}
}
Modal Reducer:
const initialState = {
showCoinModal: false
}
const coinModalData = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_COIN_MODAL:
return {
...state,
showCoinModal: !state.showCoinModal
}
default:
return state
}
}
I already figured out what's causing the delay, it's the middleware logger of redux, I just removed it and it's fast again
Related
I pass Redux state into component as per below
const mapStateToProps = state => ({
rdx_userGPlaces: state.rdx_userGPlaces,
});
..then I pass redux state into FlatList component as follows:
return (
<View>
<FlatList
data={this.props.rdx_userGPlaces}
......etc
Redux state being passed in, is an array of "place" objects.....when the child object of one of these place objects changes.....FlatList does not re-render
Below is the action creator to update redux state
export const _actRefreshGPArray = gpArray => {
return {
type: REFRESH_GOOGLE_PLACES,
payload: gpArray,
};
};
...and the reducer
const userGPlacesReducer = (state = initialState, action) => {
switch (action.type) {
case REFRESH_GOOGLE_PLACES:
return {...state, userGPlacesFiltered: action.payload};
default:
return state;
}
};
I am developing an application with react-native and redux. I want to save my favorite movies in the store. Thus I set up my actions, reducers, store, Provider, connect...
When I dispatch an action, the reducer is called and the state changes (I check with (nextState === state) however mapStateToProps is never called.
For information, the dispatch action and mapStateToProps come from the same component. This component is in a StackNavigator. Even though I only use the component, the problem remains the same.
I guessed that I was changing the state, but it is not the case I think. I have been stuck with this problem for a while, I checked all existing problems, none of the solutions solve mine.
The dispatcher:
_toggleFavorite() {
const action = { type: "TOGGLE_FAVORITE", value: this.state.film }
this.props.dispatch(action)
}
The reducer:
const initialState = { favoritesFilm: [] }
function toggleFavorite(state = initialState, action) {
let nextState
switch (action.type) {
case 'TOGGLE_FAVORITE':
const favoriteFilmIndex = state.favoritesFilm.findIndex(item => item.id === action.value.id)
if (favoriteFilmIndex !== -1) {
nextState = {
...state,
favoritesFilm: state.favoritesFilm.filter( (item, index) => index !== favoriteFilmIndex)
}
}
else {
nextState = {
...state,
favoritesFilm: [...state.favoritesFilm, action.value]
}
}
console.log('state: ', state)
console.log('nextState: ', nextState)
console.log(nextState === state)
return nextState
default:
return state
}
}
export default toggleFavorite
The connection to the store:
const mapStateToProps = (state) => {
console.log('changed')
return {
favoritesFilm: state.favoritesFilm
}
}
export default connect(mapStateToProps)(FilmDetail)
The navigator:
const SearchStackNavigator = createStackNavigator({
SearchBar: {
screen: SearchBar,
navigationOptions: {
title: 'Rechercher'
}
},
FilmDetail: {
screen: FilmDetail
}
})
export default createAppContainer(SearchStackNavigator)
App.js :
import Navigation from './src/Navigation/Navigation';
import store from './src/Store/configureStore'
import { Provider } from 'react-redux'
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<Navigation/>
</Provider>
);
}
}
The log from reducers works. Here an example:
state: – {favoritesFilm: []}
nextstate: – {favoritesFilm: Array}
false
The log ('changed') never appears nor componentDidUpdate(). In addition, nothing is re-rendered.
Thank you in advance.
As I understand that you didn't see console.log('changed') in mapStateToProps after dispatch action.
Actually mapStateToProps just run once, after dispatch change in to reducer it will send new props to component so you can track by hook
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
example:
static getDerivedStateFromProps(props, state) {
console.log('change', props);
}
For tracking Redux changes when develop I recommend use Redux devtool
https://github.com/zalmoxisus/redux-devtools-extension
This tool will save many time for you.
Solution found....... After days of looking around .....
Problem of versions:
Have react-native 0.57.4 and had react-redux 6.0.1
I changed react-redux to 5.1.0.
My routes are not triggering screen changes using the latest React Navigation v1.5.0.
I have integrated Redux in my setup as detailed in the react-navigation-redux docs. The biggest change I see here is the 'addListener' setup, although I'm not sure this is what is preventing the screen changes. My routes were working fine using v.1.0.
I see the navigation action being fired and the screen being added to the navigation state in the debugger, but the screen isn't changing.
Clicking on the button below dispatches the action, but the screen doesn't change to the About screen and stays on the Home screen.
RootNav
StackNavigator
- Home
- About
Index.js
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.navigationState,
)
const addListener = createReduxBoundAddListener("root");
class App extends Component {
render () {
return (
<View>
<RootNav navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.navigationState,
addListener,
})} />
</View>
)
}
}
BUTTON
<TouchableHighlight onPress={ () =>
this.props.dispatch(NavigationActions.navigate({
routeName: 'About'
})) }>
<Text>About</Text>
</TouchableHighlight>
STATE AFTER CLICKING BUTTON:
nav: {
key: StackRouterRoot,
index: 1,
isTransitioning: true,
routes: [
0: {routeName: 'Home'},
1: {routeName: 'About'},
],
}
How do you properly dispatch route/screen changes?
Your navigation state is not bound to the redux, considering that you're using redux-navigation. You need to create a Navigation reducer and bind it to the store.
For eg
// Nav.js
// This is your exported router, which gets the initial State
import AppNavigator from '../Navigation/AppNavigation'
const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('LaunchScreen'))
export default reducer = (state = initialState, action) => {
const newState = AppNavigator.router.getStateForAction(action, state)
return newState || state
}
and bind it to your store like this
// Store.js
const RootReducer = combineReducers(config, {
nav: Nav,
// ...other reducers
});
and finally use it in your Redux Navigation state as
function ReduxNavigation (props) {
const addListener = createReduxBoundAddListener('root')
const { dispatch, nav } = props
const navigation = ReactNavigation.addNavigationHelpers({
dispatch,
state: nav, // this is bound to redux store using mapStateToProps
addListener
})
return <AppNavigation navigation={navigation} />
}
const mapStateToProps = state => ({ nav: state.nav })
export default connect(mapStateToProps)(ReduxNavigation)
as mentioned in the docs
I'd like to refresh the data on the screen in a react native app whenever the screen appears - like in a ViewWillAppear method. I tried using the componentWillMount method but it looks like it fires once before it appears, and doesn't fire again when the view is navigated to again.
Looking at this example https://reactnavigation.org/docs/guides/screen-tracking, it looks like I can add a listener on the onNavigationStateChange method on the root navigation, but I'd like to keep the logic in the screen as it gets confusing if I move the data fetch logic for that one scree outside to the root navigator.
I've tried to follow the example and set that method to my stacknavigation but it doesn't seem to trigger.
<RootNavigation ref={nav => { this.navigator = nav; }}
onNavigationStateChange={(prevState, currentState, action) => {
// maybe here I can fire redux event or something to let screens
// know that they are getting focus - however it doesn't fire so
// I might be implementing this incorrectly
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
console.log('navigating to this screen', currentScreen);
}
}}
/>
So here's how I did it using the onNavigateStateChange.
<RootNavigation
ref={nav => { this.navigator = nav; }}
uriPrefix={prefix}
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = this.getCurrentRouteName(currentState);
const prevScreen = this.getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
this.props.emitActiveScreen(currentScreen);
{/*console.log('onNavigationStateChange', currentScreen);*/}
}
}}
/>
And in your screen you can check to see if your view will appear, note MyPage is the route name from your navigation object.
componentWillReceiveProps(nextProps) {
if ((nextProps.activeScreen === 'MyPage' && nextProps.activeScreen !== this.props.activeScreen)) {
// do your on view will appear logic here
}
}
Here is my navigator reducer.
function getCurrentRouteName(navState) {
if (!navState) {
return null;
}
const navigationState = (navState && navState.toJS && navState.toJS()) || navState;
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
export default function NavigatorReducer(state, action) {
// Initial state
if (!state) {
return fromJS(AppNavigator.router.getStateForAction(action, state));
}
// Is this a navigation action that we should act upon?
if (includes(NavigationActions, action.type)) {
// lets find currentScreen before this action based on state
const currentScreen = getCurrentRouteName(state);
const nextState = AppNavigator.router.getStateForAction(action, state.toJS());
// determine what the new screen will be after this action was performed
const nextScreen = getCurrentRouteName(nextState);
if (nextScreen !== currentScreen) {
nextState.currentRoute = nextScreen;
console.log(`screen changed, punk: ${currentScreen} -> ${nextScreen}`);
}
return fromJS(nextState);
}
return state;
}
And then we have to connect the module/route to the redux store (sceneIsActive is the important bit):
export default connect(
state => ({
counter: state.getIn(['counter', 'value']),
loading: state.getIn(['counter', 'loading']),
sceneIsActive: state.getIn(['navigatorState', 'currentRoute']) === 'Counter',
}),
dispatch => {
return {
navigate: bindActionCreators(NavigationActions.navigate, dispatch),
counterStateActions: bindActionCreators(CounterStateActions, dispatch),
};
},
)(CounterView);
And then inside your component, you can watch for/trigger code when the scene becomes active:
componentWillReceiveProps(nextProps) {
if (nextProps.sceneIsActive && (this.props.sceneIsActive !== nextProps.sceneIsActive)) {
console.log('counter view is now active, do something about it', this.props.sceneIsActive, nextProps.sceneIsActive);
doSomethingWhenScreenBecomesActive();
}
}
Know that componentWillReceiveProps does not run when initially mounted. So don't forget to call your doSomethingWhenScreenBecomesActive there as well.
You can use focus/blur event listeners(demonstrated here and discussed here).
I have a container in my React Native app and and I use it like preload to show scene Loading... before I get data from server. So I dispatch an action to fetch user data and after that I update my state I try to push new component to Navigator but I've got an error:
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`.
And I don't understand what is the best way to fix my problem.
So my container:
import myComponent from '../components'
class App extends Component {
componentDidMount() {
this.props.dispatch(fetchUser());
}
_navigate(component, type = 'Normal') {
this.props.navigator.push({
component,
type
})
}
render() {
if (!this.props.isFetching) {
this._navigate(myComponent);
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Loading...
</Text>
</View>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
isFetching: React.PropTypes.bool,
user: React.PropTypes.string
};
export default connect((state) => ({
isFetching: state.data.isFetching,
data: state.data.user
}))(App);
My reducer:
const data = (state = initialState, action) => {
switch (action.type) {
case types.USER_FETCH_SUCCEEDED:
return {
...state,
isFetching: false,
user: action.user
};
default:
return state;
}
};
Don't trigger anything that can setState inside the body of your render method. If you need to listen to incoming props, use componentWillReceiveProps
Remove this from render():
if (!this.props.isFetching) {
this._navigate(myComponent);
}
and add componentWillReceiveProps(nextProps)
componentWillReceiveProps(nextProps) {
if (!nextProps.isFetching) {
this._navigate(myComponent);
}
}