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

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.

Related

Use redux action the dispatch is not working

I have combined my react redux.
Here is my App.js
import React from 'react';
import ReduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { compose, createStore, applyMiddleware } from 'redux';
import reducers from './src/reducers';
import AppContainer from './src/navigator'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const App: () => React$Node = () => {
const store = createStore(reducers, {}, composeEnhancers(applyMiddleware(ReduxThunk)));
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
};
export default App;
src/reducers/index.js
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
LoginRedux: LoginReducer
});
If I use my action login(), I can see login action start, but I can't see dispatch start
import React from 'react';
import {
Text,
View,
TouchableOpacity,
} from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions';
const LoginScreen = ({ navigation }) => {
// console.log('see my test value', testValue)
return (
<View>
<TouchableOpacity
onPress={() => {
login();
}
}>
<View>
<Text>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
const mapStateToProps = (state) => {
const { testValue } = state.LoginRedux;
console.log('mapStateToProps testValue =>', testValue);
return { testValue };
};
export default connect(mapStateToProps, { login })(LoginScreen);
If I console.log(dispatch), it will show dispatch is not defined.
import { LOGIN } from './types';
export const login = () => {
console.log('login action start')
return (dispatch) => {
console.log('dispatch start');
// console.log(dispatch);
dispatch({ type: LOGIN, testValue: 'I am test' });
};
};
src/reducers/LoginReducer.js
import { LOGIN } from '../actions/types';
const INITIAL_STATE = {
testValue: ''
};
export default (state = INITIAL_STATE, action) => {
console.log('reducer =>', action); // I can't see the console.log
switch (action.type) {
case LOGIN:
return {
...state,
testValue: action.testValue
};
default:
return state;
}
};
I have no idea why my action dispatch is not working. Do I set something wrong ?
Any help would be appreciated.
According to Zaki Obeid help, I update like this:
the action code:
export const login = () => {
console.log('login !');
return { type: LOGIN };
};
the function component code:
import { login } from '../../actions';
export const SettingScreen = ({ navigation, login }) => {
// return view code
}
const mapDispatchToProps = dispatch => ({
// you will use this to pass it to the props of your component
login: () => dispatch(login),
});
connect(null, mapDispatchToProps)(SettingScreen);
In LoginScreen component
you will need to add mapDispatchToProps
const mapDispatchToProps = dispatch => ({
// you will use this to pass it to the props of your component
login: () => dispatch(login()),
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
Then
you will need to destructure from the props as:
const LoginScreen = ({ navigation, login }) => {
// your code
}
In actions.js
the way you use dispatch here requires a library redux-thunk and it's used for async calls.
and the normal action should do the job for you:
export const login = () => ({
type: LOGIN,
testValue: 'I am test'
})
I hope this is useful and will solve your problem,
Have a good day.
In a react-redux app, you obtain the dispatch function either from getting a hold of the store object directly (store.dispatch), or via the react-redux connect function, which will provide dispatch as an argument to a function you write and then later hook up to a component
import { connect } from 'react-redux';
const mapStateToProps = ...
const mapDispatchToProps = (dispatch) => {
return {
someHandle: () => dispatch(myActionCreator())
}
}
export const connect(mapStateToProps, mapDispatchToProps)(MyComponent)
You can't just call dispatch out of thin air -- it's not a global function.
It seems you are using the login function directly. you will have to use the props. Just change the name for confusing and use through props.
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
LoginRedux: LoginReducer
});
If I use my action login(), I can see login action start, but I can't see dispatch start
import React from 'react';
import {
Text,
View,
TouchableOpacity,
} from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions';
const LoginScreen = ({ navigation, userLogin }) => {
// console.log('see my test value', testValue)
return (
<View>
<TouchableOpacity
onPress={() => {
userLogin();
}
}>
<View>
<Text>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
const mapStateToProps = (state) => {
const { testValue } = state.LoginRedux;
console.log('mapStateToProps testValue =>', testValue);
return { testValue };
};
export default connect(mapStateToProps, { userLogin:login })(LoginScreen);

How do I use mapDispatchToProps in place of directly accessing the Redux store?

I'm having trouble figuring out why my React Native component isn't preforming dispatching any of the actions I've tried to connected to it. I believe I've correctly followed the suggested approach to defining matchDispatchToProps as an object, but none of the expected actions seem to be happening.
Everything works fine if I explicitly import store. For example
store.dispatch({type: 'INCREMENT'})
works where the examples using just
increment
fails.
How do I correctly dispatch actions using mapDispatchToProps in place of directly accessing the Redux store?
In fact, I wonder why I would't just add something like
export const counterAPI = bindActionCreators(
{ increment, reset },
store.dispatch
)
in my store.ts (no longer exporting anything else from there, except store for use by Provider) and change
import { increment, reset } from "../store"
// ...
export default connect(null, mapDispatchToProps)(DemoCounter)
in DemoCounter.tsx to just
import { counterAPI } from "../store"
// ...
export default connect(null)(DemoCounter)
That seems to be a lot simpler and to achieve exactly the right level of modularity.
DemoCounter.tsx:
import React, { Component } from 'react'
import { View, Button, Text } from 'native-base'
import {connect} from "react-redux"
import { increment, reset } from "../store"
export class DemoCounter extends Component {
private timerID: number = 0
private interval = 1000
private startTimer(): void {
clearInterval(this.timerID)
this.timerID = setInterval(() => {
increment // Does nothing
}, this.interval)
}
componentDidMount(): void {
this.startTimer()
}
render() {
return (
<View>
<Button onPress={increment}> /* Does nothing */
<Text>Reset A</Text>
</Button>
<Button onPress={() => {reset(); this.startTimer()}}> /* How to combine action with other behaviors? */
<Text>Reset B</Text>
</Button>
<Button onPress={reset}> /* Does nothing */
<Text>Reset C</Text>
</Button>
</View>
)
}
}
const mapDispatchToProps = {
increment,
reset,
}
export default connect(null, mapDispatchToProps)(DemoCounter)
store.ts:
import {createStore} from "redux"
interface CounterState {
count: number;
}
const initialState: CounterState = {count: 0}
export type CounterAction =
| { type: 'INCREMENT' }
| { type: 'RESET' }
export const increment = (): CounterAction => ({ type: "INCREMENT" })
export const reset = (): CounterAction => ({ type: "RESET" })
const counterReducer = (state = initialState, action: CounterAction): CounterState => {
switch (action.type) {
case 'INCREMENT':
return {...state, count: state.count + 1}
case "RESET":
return {...state, count: 1}
default:
return state
}
}
export const store = createStore(counterReducer)
App.tsx:
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { store } from "./store"
import PerspectiveCounter from "./components/PerspectiveCounter"
export default class App extends Component {
render() {
return (
<Provider store={store}>
<DemoCounter />
</Provider>
)
}
}
Hmmm...
I have never done it like that, so I am not sure what the problem is...
How about trying to define it my way? ;)
At least as a temporary workaround.
const mapDispatchToProps = (dispatch) => {
return {
increment: () =>
dispatch({ type: '"INCREMENT"'})
}
}
Any calling this.props.increment, of course.

Reducer not changing state

Below are the relevant files.
In the reducer, when it runs...
return {
loggedIn: action.loggedIn
};
I was expecting it to replace the state with that information.
When I run this code in LoginForm I get the old state output.
this.props.onLogin();
console.log(this.props.loggedIn);
I'm hoping I'm overlooking something simple here. Everything else seem to work the way I was expecting it to. I can change the state directly in the
switch using...
state.loggedIn = action.loggedIn;
And it works as expected. Can anyone shed some light on what I am doing wrong?
Action
import { LOGGED_IN } from './actionTypes';
export const loggedIn = () => {
return {
type: LOGGED_IN,
loggedIn: true,
};
};
Reducer
import {
LOGGED_IN
} from "../actions/actionTypes";
const initialState = {
loggedIn: false,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case LOGGED_IN:
return {
loggedIn: action.loggedIn
};
default:
return state;
}
};
export default reducer;
import React, { Component } from 'react';
import { Text } from 'react-native';
import { Button, Card, CardSection, Input, Spinner } from './common';
import { Actions } from 'react-native-router-flux';
import firebase from '../Fire';
import { connect } from "react-redux";
import {
loggedIn
} from "../store/actions";
LoginForm
class LoginForm extends Component {
onButtonPress() {
this.onLoginSuccess();
}
onLoginSuccess() {
this.props.onLogin();
console.log(this.props.loggedIn);
Actions.main({});
}
renderButton() {
return (
<Button onPress={this.onButtonPress.bind(this)}>
Log in
</Button>
);
}
render() {
return (
<Card>
<CardSection>
<Input
placeholder="user#gmail.com"
label="Email"
</CardSection>
<CardSection>
<Input
secureTextEntry
placeholder="password"
label="Password"
/>
</CardSection>
<CardSection>
{this.renderButton()}
</CardSection>
</Card>
);
}
}
const styles = {
errorTextStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
};
const mapStateToProps = state => {
return {
loggedIn: state.loggedIn
};
};
const mapDispatchToProps = dispatch => {
return {
onLogin: () => dispatch(loggedIn()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
configureStore
import { createStore, combineReducers } from 'redux';
import prolinkReducer from './reducers/prolink';
const rootReducer = combineReducers({
loggedIn: prolinkReducer
});
const configureStore = () => {
return createStore(rootReducer);
};
export default configureStore;
The reason that you are getting the previous value is that you are console logging the previous value.
When the onLoginSuccess is called the current value for this.props.loggedIn will be passed to the console.log. I imagine if you set a long enough timeout on it then it would show that it is being updated, but that is not exactly the best way to check.
componentDidUpdate
If you want to check that your redux state is updating you should check what is happening in the componentDidUpdate https://reactjs.org/docs/react-component.html#componentdidupdate
As you subscribe to loggedIn in your mapStateToProps in your LoginForm.js, that means your component will receive the new value for loggedIn once it is updated.
In your LoginForm.js add the following:
componentDidUpdate(prevProps, prevState) {
console.warn('previous', prevProps.loggedIn, 'current', this.props.loggedIn)
}
This will allow you to see the values for loggedIn as it changes.
react-native-debugger
You could use react-native-debugger which includes redux inspection tools. https://github.com/jhen0409/react-native-debugger. This allows you to see your redux store in real-time, meaning you can easily track the changes without having to resort to checking in the componentDidUpdate. However, at this time there is currently an issue with react-native-debugger that means it is not working with react-native 0.58.+, though there is an open pull request that fixes the issue.
middleware
Alternatively you could add a middleware to your redux setup that logs each event to your console. https://redux.js.org/advanced/middleware, I have previously used redux-logger it is quite customisable, and depending on your use cases you may find it suits your needs.

#react-native [0.42] - New Navigation with Redux - Cannot map state to props

RN: 0.42
I tried to use the new navigation (that was released) + redux and I am unable to map the initial state of the redux to props, in a screen where the store is passed.
I followed this: https://reactnavigation.org/docs/guides/redux
I have written my custom reducer.
export const types = {
...
}
export const actionCreators = {
authenticate: () => async (dispatch, getState) => {
...
}
}
const initialState = {
auth: {
...
}
}
export const reducer = (state = initialState, action) => {
const {auth} = state
const {type, payload, error} = action
switch (type) {
...
}
return state
}
In index.ios.js I have combined my own custom reducer
import { addNavigationHelpers } from 'react-navigation';
import * from 'appReducer';
const AppNavigator = StackNavigator({
Home: { screen: MyTabNavigator },
});
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return (newState ? newState : state)
};
const appReducer = combineReducers({
nav: navReducer,
app: appReducer
});
#connect(state => ({
nav: state.nav,
}))
class AppWithNavigationState extends React.Component {
render() {
return (
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
);
}
}
const store = createStore(appReducer);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
}
Inside Home.js, 'mapStateToProps' does not work.
import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import { connect } from 'react-redux'
import { actionCreators } from './appRedux'
const mapStateToProps = (state) => ({
//This is the problem: Here 'state' has no 'auth' object attached to it
auth: state.auth
})
class Home extends Component {
componentWillMount() {
const {dispatch} = this.props
//Dispatch works
dispatch(actionCreators.authenticate('testId', 'testToken'))
}
render() {
const {auth} = this.props
return (
<View style={styles.container}>
<Text>
Welcome {auth['name']}
</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
}
})
export default connect(mapStateToProps)(Home)
Note, the dispatch function is available to fire but the 'state' does not have the reducer 'initialState' attached to it.
Please let me know the correct way to attach the reducer initialState to various Screens in the new RN navigation.
I got it where you are doing wrong, in your index.ios.js change import * from 'appReducer'; to import {reducer} from 'appReducer'; then your current combined reducer function will be like
const appReducer = combineReducers({
nav: navReducer,
app: reducer
});
then in your home.js your mapStateToProps should be like
const mapStateToProps = (state) => ({
//This is the problem: Here 'state' has no 'auth' object attached to it
authState: state.app //remember store only contains reducers as state that you had passed in combineReducer function
})
now use it in your component like
this.props.authState.auth //remember you had wrapped your auth object within initialState object

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.