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

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.

Related

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.

the global state won't update by dispatch reducer

index.js
import React from 'react';
import {
AppRegistry
} from 'react-native'
import App from './App';
import { YellowBox } from 'react-native';
YellowBox.ignoreWarnings(['Warning: isMounted(...) is deprecated', 'Module RCTImageLoader']);
AppRegistry.registerComponent('mhagora', () => App);
App.js
import React, { Component } from 'react';
import { Provider } from "react-redux";
import store from './app/store';
import { StyleProvider, getTheme } from "native-base";
import Setup from "./app/setup";
import variables from "./app/theme/variables/commonColor";
export default class App extends Component {
render() {
return (
<Provider store={store}>
<StyleProvider style={getTheme(variables)}>
<Setup />
</StyleProvider>
</Provider>
);
}
}
./app/setup.js
import React, { Component } from "react";
import axios from "axios/index";
import Config from "./config";
import { Root } from "native-base";
import AppNavigator from "./routes";
axios.defaults.baseURL = Config.API_BASE_URL;
axios.defaults.headers.common['Content-Type'] = Config.API_ACCEPT;
axios.defaults.headers.common['Accept'] = Config.API_ACCEPT;
axios.defaults.headers.common['secret'] = Config.API_SECRET;
export default class Setup extends Component {
render() {
return (
<Root>
<AppNavigator />
</Root>
);
}
}
./app/store/index.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import reducers from '../reducers';
const logger = createLogger();
export default createStore(reducers, compose(applyMiddleware(thunk, logger)));
./app/actions/index.js
import { APP_LOADING, APP_LOADED } from '../actionTypes';
export function appLoading() {
return (dispatch) => {
dispatch({type: APP_LOADING});
}
}
export function appLoaded() {
return (dispatch) => {
dispatch({type: APP_LOADED});
}
}
./app/actions/user.js
import { USER_LOADING, USER_LOADED, USER_FAILED, APP_LOADING, APP_LOADED } from "../actionTypes";
import axios from 'axios';
import Config from '../config';
export function userLogin(username, password) {
return (dispatch) => {
dispatch({type: USER_LOADING});
axios
.post("oauth/token", {
username: username,
password: password,
client_id: Config.API_CLIENT_ID,
client_secret: Config.API_CLIENT_SECRET,
grant_type: 'password',
}, {
headers: {}
})
.then(response => {
dispatch({
type: USER_LOADED,
data: response.data
});
})
.catch(err => {
dispatch({ type: USER_FAILED, error: err.response.data.message });
alert(err.response.data.message);
});
};
}
./app/reducers/index.js
import appReducer from './appReducer';
import userReducer from './userReducer';
import { combineReducers } from "redux";
const rootReducer = combineReducers({
appReducer,
userReducer
});
export default rootReducer;
./app/reducers/userReducer.js
import { USER_LOADING, USER_LOADED, USER_FAILED } from '../actionTypes';
const initialState = {
username: "",
password: "",
user: {}
};
export default userReducer = (state = initialState, action) => {
switch (action.type) {
case USER_LOADING:
return Object.assign({}, state, {
loading: true,
user: {},
});
case USER_LOADED:
return Object.assign({}, state, {
loading: false,
user: action.data
});
case USER_FAILED:
return Object.assign({}, state, {
loading: false,
});
default:
return state
}
}
./app/reducers/appReducer.js
import { APP_LOADING, APP_LOADED } from "../actionTypes";
const initialState = {
loading: true,
};
export default appReducer = (state = initialState, action) => {
switch (action.type) {
case APP_LOADING:
return Object.assign({}, state, {
loading: true
});
case APP_LOADED:
return Object.assign({}, state, {
loading: false
});
default:
return state;
}
};
./app/screens/home.js
'use strict';
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { SkypeIndicator } from 'react-native-indicators';
import * as Actions from '../actions/index';
import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon, Text, View } from 'native-base';
class HomeScreen extends Component {
componentDidMount() {
/** HERE, the apps should show a loading page forever but it didn't **/
// setTimeout( _ => {
// this.props.appLoaded();
// }, 2000);
}
render() {
if (this.props.loading) {
return (
<SkypeIndicator />
);
} else {
return (
<Container>
<Header>
</Header>
<Body>
<Button
onPress={() =>
this.props.navigation.navigate('LoginScreen')
}><Text>Login now</Text></Button>
<Text>Hello</Text>
</Body>
</Container>
);
}
}
}
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.loading,
user: state.user,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/homeScreen.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen);
./app/screens/loginScreen
'use strict';
import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { SkypeIndicator } from 'react-native-indicators';
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { Body, Button, Container, Content, Header, Icon, Left, Text, Title, View } from "native-base";
import t from 'tcomb-form-native';
import { LoginUserModel, LoginUserModelOption } from "../models/UserModel";
import styles from '../styles';
import LoadingButton from 'react-native-loading-button';
import * as UserActions from '../actions/user';
const Form = t.form.Form;
const ps = StyleSheet.create({
...styles,
container: {
justifyContent: 'center',
marginTop: 50,
padding: 20
},
});
class LoginScreen extends Component {
constructor(props) {
super(props);
}
onSubmitHandler = () => {
const value = this._form.getValue();
if(value) {
this.props.userLogin(value.username, value.password);
}
};
render() {
return (
<Container>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name="arrow-back"/>
</Button>
</Left>
<Body>
<Title>Headers</Title>
</Body>
</Header>
<Content padder>
<View style={ps.container}>
<Form ref={c => this._form = c} type={LoginUserModel} options={LoginUserModelOption} />
<LoadingButton
block
onPress={this.onSubmitHandler.bind(this)}
isLoading={this.props.loading}
style={{ justifyContent: 'center' }}
><Icon name="checkmark"/><Text>Login Now</Text></LoadingButton>
</View>
</Content>
</Container>
);
}
}
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.loading,
user: state.user,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/homeScreen.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
the homeScreen should result in a forever loading page but it didn't
the loginScreen button should automatically loading when pressing, but it didn't
new to react-native, i have tried to set/play with the state/props but it just seems like not changing/connected, i also have another page trying to check the state is synced but results is like always get the fresh state, as my understanding the state is something like GLOBAL variable accessible in any component connect to redux
MY QUESTION IS
1. is the react-native/redux/redux-thunk setup correctly? if not, where is the error
2. is the state/props is global accessible in any component that connect with redux
3. if statement 2 is correct, what the different between state/props? this.state and this.props
4. i don't really understand the promise work, how can we handle / wait untill the api call complete(success/error) before move to next step/flow, i use php a lot and my logic is stuck at each function should return something then depends on the results process to next function...and then...
your answer / precious time spend for reading this question is appreciated, thank you
created a github for easy to reproduce/test
https://github.com/weiloon1234/react-native-test

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.

Issue accessing store and calling actionCreators

I'm in the process of learning React Native with Redux and can't wrap my head around how to access the store/actionCreators from within my components. I feel like I've tried ten or so variants of code, watched most of Dan Abramov's Egghead videos (multiple times), and still don't understand what I'm doing wrong. I think the best explanation I've seen so far is the accepted answer here: Redux, Do I have to import store in all my containers if I want to have access to the data?
The error I get is: undefined is not an object (evaluating 'state.clinic.drName'). Here are the relevant bits of code:
I'm passing the store via the Provider (so I think):
index.ios.js
import clinicReducer from './reducers/clinic';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
const rootReducer = combineReducers({
clinic : clinicReducer
});
let store = createStore(combineReducers({clinicReducer}));
//testing - log every action out
let unsubscribe = store.subscribe( () =>
console.log(store.getState())
);
class ReactShoeApp extends React.Component {
render() {
return (
<Provider store={store}>
<ReactNative.NavigatorIOS
style={styles.container}
initialRoute={{
title: 'React Shoe App',
component: Index
}}/>
</Provider>
);
}
}
Here is my actionCreator:
export const CLINIC_DR_NAME_UPDATE = 'CLINIC_DR_NAME_UPDATE_UPDATE';
export function updateClinicDrName(newValue) {
return {type: CLINIC_DR_NAME_UPDATE, value: newValue};
}
Here is my reducer:
import {
CLINIC_DR_NAME_UPDATE
} from '../actions/';
let cloneObject = function(obj) {
return JSON.parse(JSON.stringify(obj));
};
const initialState = {
drName : null
};
export default function clinicReducer(state = initialState, action) {
switch (action.type) {
case CLINIC_DR_NAME_UPDATE:
return {
...state,
drName: action.value
};
default:
return state || newState;
}
}
and here is my component:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
const mapStateToProps = function(state){
return {
drName: state.clinic.drName,
}
};
const mapDispatchToProps = function (dispatch) {
return bindActionCreators({
updateClinicDrName: Actions.updateClinicDrName,
}, dispatch)
};
//class definition
class Clinic extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.rowContainer}>
<View style={styles.row}>
<TextGroup label={'Dr. Name'} placeholder={'placeholder'} value={this.props.drName} onChangeText={(text) => this.handlers.onNameChange({text})}></TextGroup>
<TextGroup label={'City'} placeholder={'placeholder'}></TextGroup>
</View>
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Clinic);
All I'm trying to do is update the store (clinic.drName) when the user types in the Dr Name Text Group. Any and all help is appreciated.
The issue is you're mismatching the way you call combineReducers() with the name of the slice you're expecting. This is a common mistake. You're defining an object with the key clinicReducer, but expecting the data to be at a key named clinic.
See Defining State Shape for an explanation of the problem.
Also, your clinicReducer() function looks odd, and has several issues:
Don't access variables outside the function like you are with newState.
Don't do deep-cloning like that (per Redux FAQ: Performance). Instead, use "shallow cloning" to update the state if necessary (see examples at Immutable Update Patterns).
It looks like the reducer is double-nesting the data it's trying to access as well.
I think you want something more like:
// clinicReducer.js
const initialState = {
drName : null
};
export default function clinicReducer(state = initialState, action) {
switch(action.type) {
case CLINIC_DR_NAME_UPDATE: {
return {
...state,
drName : actino.value
};
}
default: return state
}
}
// index.js
import clinicReducer from "./reducers/clinic";
const rootReducer = combineReducers({
clinic : clinicReducer
});
What errors are you getting? I see two issues with what you have.
newState.clinic.drName = action.value; should be throwing a typeError. So for this to work you need to change newState to:
const newState = {
clinic: {}
}
What is this.handlers.onNameChange({text})? I think you need to change this to this.props.updateClinicDrName(text).

State change not making it to the child container

In my react native app I'm using redux to handle state transition of a Post object -- the state is changed by couple of child components. The Post object has properties like title, name, description which the user can edit and Save.
In the reducer Im using React.addons.update return new state object.
The main container view has 2 custom child components (wrapped in TabBarNavigator).
One of the child component has few TextInputs which is updating a state.
Using the logger middleware and console.log() I see the new state value in the parent view's render() (via this.props.name) but not in the child view.
I'm trying to figure out why the updated state is not propagated to the child container. Any suggestion is much appreciated.
Im at a point where Im thinking of subscribeing to the redux store manually in the child container but it feels wrong
my code looks like this:
MainView
Reducer
configure store etc
The MainView
const React = require('react-native');
const {
Component,
} = React;
const styles = require('./../Styles');
const MenuView = require('./MenuView');
import Drawer from 'react-native-drawer';
import TabBarNavigator from 'react-native-tabbar-navigator';
import BackButton from '../components/BackButton';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as PostActions from '../actions/Actions';
import {Details} from './Article/Details';
import {ArticleSecondary} from './Article/Secondary';
var update = require('react-addons-update');
import configureStore from '../store/configureStore';
class ArticleMainView extends Component {
constructor(props){
super(props);
//var store = configureStore(props.route.post);
this.state = {
};
}
componentDidMount(){
}
savePost() {
console.log(this.props.post.data);
this.props.navigator.pop();
}
render(){
console.log("ArticleMainView: render(): " + this.props.name);
return(
<TabBarNavigator
ref="navComponent"
navTintColor='#346293'
navBarTintColor='#94c1e8'
tabTintColor='#101820'
tabBarTintColor='#4090db'
onChange={(index)=>console.log(`selected index ${index}`)}>
<TabBarNavigator.Item title='ARTICLE' defaultTab>
<Details ref="articleDetail"
backButtonEvent={ () => {
this.props.navigator.pop();
}}
saveButtonEvent={ () => {
this.savePost();
}}
{...this.props}
/>
</TabBarNavigator.Item>
<TabBarNavigator.Item title='Secondary'>
<ArticleSecondary ref="articleSecondary"
{...this.props}
backButtonEvent={ () => {
this.props.navigator.pop();
}}
saveButtonEvent={ () => {
this.savePost();
}}
/>
</TabBarNavigator.Item>
</TabBarNavigator>
);
}
}
function mapStateToProps(state) {
return {
post: state,
text: state.data.text,
name: state.data.name,
description: state.data.description
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(PostActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ArticleMainView);
The Reducer:
import {Constants} from '../api/Constants';
var update = require('react-addons-update');
export default function postReducer(state, action) {
switch(action.type) {
case Constants.SET_POST_TEXT:
if( state.data.text){
return update(state, {
data: { $merge: {text: action.text }}
});
}else{
return update(state, {
data: { $merge: {text: action.text }}
});
}
break;
case Constants.SET_POST_NAME:
return update(state, {
data: { name: { $set: action.text }}
});
return newO;
break;
case Constants.SET_POST_DESCRIPTION:
return update(state, {
data: { description: { $set: action.text }}
});
break;
default:
return state;
}
}
render scene of the app:
renderScene(route, navigator) {
switch (route.id) {
case "ArticleMainView":
let store = configureStore(route.post);
delete route.post; // TODO: not sure if I should remove this
return (
<Provider store={store}>
<ArticleMainView navigator={navigator} {...route}/>
</Provider>
);
default:
return <LandingView navigator={navigator} route={route}/>
}
}
configureStore:
import { createStore,applyMiddleware,compose } from 'redux'
import postReducer from '../reducers/SocialPostReducer';
import createLogger from 'redux-logger';
const logger = createLogger();
export default function configureStore(initialState){
return createStore(
postReducer,
initialState,
compose(applyMiddleware(logger))
);
}
If anyone stumbles on this question this is how I solved it. In each of the child components I declared a contextTypes object like so
ChildComponentView.contextTypes = {
store: React.PropTypes.object
}
to access the current state in the child component
let {store} = this.context;
store.getState();
I don’t know React Native well but something that threw me off is that you’re effectively creating a store on every render:
case "ArticleMainView":
let store = configureStore(route.post);
delete route.post; // TODO: not sure if I should remove this
return (
<Provider store={store}>
<ArticleMainView navigator={navigator} {...route}/>
</Provider>
);
Store should only be created once per application lifetime. It never makes sense to create it inside render() or renderScene() or similar methods. Please check the official Redux examples to see how the store is typically created.
Another problem is that you don’t show how you update the data, which child component doesn’t get updated, when you expect it to get updated, and so on. This is a lot of code, and it is very hard to help because it is incomplete, and most of it is not relevant to the problem. I would suggest you to remove all the irrelevant code until you can reproduce the problem with a minimal possible complete example. Then you can amend your question to include that example.