State changes by dispatching an action but mapStateToProps is not called - react-native

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.

Related

Redux mapStateToProps call only once

It's been 2 days that I'm stuck with an issue implementing redux in my react native application. I checked many topics but I can't fix my problem.
The problem is that mapStateToProps is called only once but it's not called anymore after executing action. My reducer is called but the state is not updating.
I test my work on android with android studio and emulator.
Here is my code :
App.js:
<Provider store={createStore(reducers)}>
<View>
<MainScreen />
</View>
</Provider>
HeaderReducer:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SET_HEADER:
return action.payload;
default:
return state;
}
};
reducers/index.js
import { combineReducers } from 'redux';
import HeaderReducer from './HeaderReducer';
export default combineReducers({
headerText: HeaderReducer,
});
actions/index.js
export const SET_HEADER = 'set_header';
export const setHeaderText = (title) => {
return {
type: SET_HEADER,
payload: title,
};
};
MainScreen.js
import * as actions from '../actions';
...
<Text> {this.props.headerText} </Text>
<Button
onPress={() => {
this.props.setHeaderText(`screen: ${SELECTED_PAGE.profile}`);
this.forceUpdate();
}}
>
...
const mapStateToProps = (state) => {
const { headerText } = state;
return { headerText };
};
export default connect(mapStateToProps, actions)(MainScreen);
I know that I don't need to forceUpdate in the onPress callback but it's just to check the state after call the function.
So when the application start the reducers are called and my mapStateToProps is working. But when I click on my button, my reducers are called, the good action is executed in the switch but my mapStateToProps is not called anymore. When I click a second time if I console.log the state in my reducer I can see that the state is not updated but stay the same than the INITIAL_STATE.
I don't mutate the state in my reducers, I connected everything in my MainScreen.js and I wrapped my app in Provider with store.
I follow a tutorial on Udemy to implement reduc in my app and even I check the code of the tuto on github, I can't find out where the issue is from (https://github.com/StephenGrider/ReactNativeReduxCasts/tree/master/tech_stack)
I think I miss something really obvious but I can't find out what's wrong since 2 days :/
If someone can help, I would appreciate :)
Thanks.
EDIT :
I didn't figure out the problem, so I just restart from scratch, I did exactly the same (THE SAAME!!) and it's working now... Thanks for your help :)
The problem is in your HeaderReducer, reducer is expecting to return a state object, but you're returning a string instead.
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SET_HEADER:
return action.payload;
default:
return state;
}
During the case of SET_HEADER, what you really want is to overwrite a field instead of overwrite the whole state object, so change to below instead
const INITIAL_STATE = { customizeText: '' };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SET_HEADER:
return { ...state, customizeText: action.payload }; // <<=== HERE
default:
return state;
}
}
Then in your MainScreen.js, you have to update your mapStateToProps function
<Text> {this.props.someText} </Text>
...
const mapStateToProps = ({ headerText }) => {
return { someText: headerText.customizeText };
};
...
I've updated the field name to avoid confusion
First make an actual action as this.props.setHeaderText() and pass title if you wish to and make sure your INITIAL_STATE is an object or [].
const mapStateToProps = (state) => {
return{
headerText: state.headerText
}
};
export default connect(mapStateToProps, actions)(MainScreen);

I can only parse my mapStateToProps object to an extent inside of my react component

I am pretty new to redux and am having trouble parsing JSON data, when I mapStateToProps inside my react component. For instance, if I console.log(this.props.chartData[0]) in my react component, the console will display the array I am trying to access, however, when I try to access a specific element in the array by console logging (this.props.ChartData[0].title), I get an error:
[enter image description here][1]
class ChartContainer extends Component {
componentWillMount(){
this.props.chartChanged();
}
render(){
console.log(this.props.chartData[0]);
return(
<Text style={styles.textStyle}>
test
</Text>
);
}
}
const mapStateToProps = state => {
return {
chartData: state.chart
}
};
export default connect (mapStateToProps, {chartChanged}) (ChartContainer);
Interestingly, I have no problem accessing(this.props.ChartData[0].title) inside my reducer.
import {CHART_CHANGED} from '../actions/types';
const INITIAL_STATE = { chartData: [] };
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
case CHART_CHANGED:
console.log("action");
console.log(action.payload[0].title);
return{...state, chartData: action.payload};
default:
return state;
}
};
Here is the api call in my action file:
export const chartChanged = (chartData) => {
return (dispatch) => {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then((chartData) =>{
dispatch({type: CHART_CHANGED, payload: chartData.data});
});
};
};
If someone can explain why this is happening, I would be super grateful.
So the problem is that you shouldn't assign any value during fetching, what you need to do is use lodash and try doing something like this
import _ from 'lodash'
const title = _.get(this.props.ChartData, 'chartData', [])
if(!isFetching){
//do something
}

Component's prop doesn't update in React Native with Redux

I need some help with my app and Redux! (Currently, i hate it aha)
So, i have a notification page component which fetch some datas and i need to put the data length into my redux store to put badge on my icon in my tabbar!
My Main Reducer :
import { combineReducers } from "redux";
import NotificationReducer from "./NotificationReducer";
export default function getRootReducer(navReducer) {
return combineReducers({
nav: navReducer,
notificationReducer: NotificationReducer
});
}
My Notification reducer
const initialState = {
NotificationCount: 0
};
export default function notifications(state = initialState, action = {}) {
switch (action.type) {
case 'SET_COUNT' :
console.log('REDUCER NOTIFICATION SET_COUNT',state)
return {
...state,
NotificationCount: action.payload
};
default:
return state;
}
};
My Action :
export function setNotificationCount(count) {
return function (dispatch, getState) {
console.log('Action - setNotificationCount: '+count)
dispatch( {
type: 'SET_COUNT',
payload: count,
});
};
};
My Component :
import React, { Component } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, SectionList, Alert } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { Notification } from '#Components';
import { ORANGE } from '#Theme/colors';
import { NotificationService } from '#Services';
import Style from './style';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '#Redux/Actions';
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
export class NotificationsClass extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
NotificationCount: undefined
};
}
async componentWillMount() {
this.updateNotifications();
}
componentWillReceiveProps(nextProps){
console.log('receive new props',nextProps);
}
async updateNotifications() {
this.props.setNotificationCount(10); <---
let data = await NotificationService.get();
if (data && data.data.length > 0) {
this.setState({ dataSource: data });
console.log(this.props) <-- NotificationCount is undefined
}
}
render() {
if (this.state.dataSource.length > 0) {
return (
<SectionList
stickySectionHeadersEnabled
refreshing
keyExtractor={(item, index) => item.notificationId}
style={Style.container}
sections={this.state.dataSource}
renderItem={({ item }) => this.renderRow(item)}
renderSectionHeader={({ section }) => this.renderSection(section)}
/>
);
} else {
return this.renderEmpty();
}
}
renderRow(data) {
return (
<TouchableOpacity activeOpacity={0.8} key={data.notificationId}>
<Notification data={data} />
</TouchableOpacity>
);
}
}
const Notifications = connect(
state => ({
NotificationCount: state.NotificationCount
}),
dispatch => bindActionCreators(Actions, dispatch)
)(NotificationsClass);
export { Notifications };
(I've removed some useless code)
Top Level :
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})}
/>
);
}
}
const store = getStore(navReducer);
export default function NCAP() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
React : 15.6.1
React-Native : 0.46.4
Redux : 3.7.2
React-Redux : 5.0.5
React-Navigation : 1.0.0-beta.11
Node : 6.9.1
So if you've an idea! It will be great :D !
Thanks !
There's three issues.
First, React's re-rendering is almost always asynchronous. In updateNotifications(), you are calling this.props.setNotificationCount(10), but attempting to view/use the props later in that function. Even with the await in there, there's no guarantee that this.props.NotificationCount will have been updated yet.
Second, based on your reducer structure and mapState function, props.NotificationCount will actually never exist. In your getRootReducer() function, you have:
return combineReducers({
nav: navReducer,
notificationReducer: NotificationReducer
});
That means your root state will be state.nav and state.notificationReducer. But, in your mapState function, you have:
state => ({
NotificationCount: state.NotificationCount
}),
state.NotificationCount will never exist, because you didn't use that key name when you called combineReducers.
Third, your notificationReducer actually has a nested value. It's returning {NotificationCount : 0}.
So, the value you actually want is really at state.notificationReducer.NotificationCount. That means your mapState function should actually be:
state => ({
NotificationCount: state.notificationReducer.NotificationCount
}),
If your notificationReducer isn't actually going to store any other values, I'd suggest simplifying it so that it's just storing the number, not the number inside of an object. I'd also suggest removing the word Reducer from your state slice name. That way, you could reference state.notification instead.
For more info, see the Structuring Reducers - Using combineReducers section of the Redux docs, which goes into more detail on how using combineReducers defines your state shape.

Get warning after updating component in Navigator

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

React Native render not being triggered after Redux action is dispatched

I'm having this issue, the action is being dispatched, the reducer is being executed but the render function is not being triggered.
Container:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { renderVoterSearch } from '../actions';
import Search from '../components/Search';
class SearchContainer extends Component {
render() {
return (
<Search {...this.props}/>
);
}
}
const mapStateToProps = (state) => {
return {
searchType: state.searchType,
instruction: state.instruction,
title: state.title
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
renderVoterSearch
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchContainer);
The action dispatched from the Search Component
export const renderVoterSearch = (tab) => ({
type: RENDER_VOTER_SEARCH,
searchType: tab.type,
instruction: tab.instruction,
title: tab.title
});
Reducer:
const search = (state = initialState, action) => {
switch (action.type) {
case RENDER_VOTER_SEARCH:
return {
...state,
searchType: action.searchType,
instruction: action.instruction,
title: action.title
}
default:
return state
}
}
Here's the complete code
https://github.com/mivotico/mivotico-react-native/tree/redux-first-steps/app
I've been reading that one of the reasons may be that the state is being mutated but already checked and didn't see any mutation.
Thanks in advance!
Found the issue! Was that I didn't know the reducers were inside the state, so if the reducer is called "search" then in the mapStateToProps function to access this state should be state.search instead of just state.