I'm using the new react-navigation library for a React Native application I'm building. I'm having an issue with passing down my ActionCreators from my Nav component down to its scenes.
I have an AppContainer that wraps the entire application.
import React, { Component } from 'react';
import { DrawerNavigator, addNavigationHelpers } from 'react-navigation';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ActionCreators } from '../actions';
import DashboardContainer from './DashboardContainer';
import CustomersContainer from './CustomersContainer';
const ApplicationNavigation = DrawerNavigator({
Dashboard: { screen: DashboardContainer },
Customers: { screen: CustomersContainer },
});
class AppContainer extends Component {
render() {
return (
<ApplicationNavigation />
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(() => { return {} }, mapDispatchToProps)(AppContainer);
Here is the CustomerContainer:
import React, {Component} from 'react';
import {View, Text, Button} from 'react-native';
export default class CustomerContainer extends Component {
btnPressed() {
this.props.listCustomers()
}
render () {
return (
<View style={{marginTop: 40}}><Text>Customer</Text>
<Button onPress={() => this.btnPressed()} title="Press Me!" />
</View>
);
}
}
Now I'm trying to call an action within my CustomerContainer this.props.listCustomers(). The problem is the ActionCreator props aren't being passed down to the screens. I've tried doing adding the screenProps prop to the ApplicationNavigation component:
But for some reason when I do this my app doesn't display any screens its just blank with no errors.
UPDATE
So I updated my CustomerContainer file:
import React, {Component} from 'react';
import {View, Text, Button} from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ActionCreators } from '../actions';
class CustomerContainer extends Component {
btnPressed() {
console.log(this.props.listCompanyCustomers())
}
render () {
return (
<View style={{marginTop: 40}}><Text>Customer</Text>
<Button onPress={() => this.btnPressed()} title="Press Me!" />
</View>
);
}
}
function mapStateToProps(state) {
return {
companyCustomers: state.companyCustomers
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomerContainer);
This now works; however this feels like the incorrect way to go about this.
What redux connect basically does is:
Wrap your component
Declare contextProps to get access to dispatch
pass the dispatch to your component props.
If you pass the connect mapDispatchToProps, then it creates the mapping of the methods to dispatch.
So if you connect you AppContainer, its children won't get these dispatch methods, unless AppContainer passes them to its children (but this what connect comes to prevent).
So to sum up, you should connect any component that needs to use dispatch, otherwise it won't get it.
If you don't want to copy paste the mapDispatchToProps, you can just delete it and use this.props.dispatch instead:
import { ActionCreators } from '../actions';
class CustomerContainer extends Component {
btnPressed() {
this.props.dispatch(ActionCreators.listCompanyCustomers());
}
render () {
return (
<View style={{marginTop: 40}}><Text>Customer</Text>
<Button onPress={() => this.btnPressed()} title="Press Me!" />
</View>
);
}
}
Related
My problem is That I want to access a Container in a component but it seems to be undefined.
undefined alert image
I am using Unstated and as you can see this is my code in the container file (login-container.js):
import { Container } from 'unstated'
class LoginContainer extends Container {
constructor(props){
super(props)
this.state = {
stepNumber: 0,
}
}
}
export default new LoginContainer()
And this is app.js:
import React, { Component } from 'react'
import { createStackNavigator, createSwitchNavigator } from 'react-navigation'
import { Provider } from 'unstated'
import LoginContainer from './containers/login-container'
import Home from './screens/home'
import Splash from './screens/splash'
import Login from './screens/login'
import Intro from './screens/intro'
export default class App extends Component {
render() {
return (
<Provider inject={[LoginContainer]}>
<AuthStack/>
</Provider>
)
}
}
const SplashStack = createStackNavigator(...)
const AppStack = createStackNavigator(...)
const AuthStack = createStackNavigator(
{
Intro: { screen: Intro},
Login: { screen: Login}
},
{
headerMode: "none",
initialRouteName: "Intro"
}
)
const SwitchNavigator = createSwitchNavigator(...)
And this would be login.js:
import React, { Component } from 'react'
import { Text, View } from 'react-native'
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
// const { state: {stepNumber} } = this.props.loginContainer
alert(this.props.LoginContainer)
return (
<View>
<Text> someText </Text>
</View>
)
}
}
I previously tried to use Subscribe component to inject the container to my app but I got the same thing I am getting here.
Using
- react-native 0.58.6
- react-navigation 2.13.0 (due to some bugs in v3)
- unstated 2.1.1
What's really great about Unstated is how simple it is to implement.
Just wrap your render component in Unstated's <Subscribe to></Subscribe> tags and you're good to go. Whenever you setState() in the Container, all Components that Subscribe to it get re-rendered with the Container's updated state property values available to them.
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
return (
<Subscribe to={[LoginContainer, AnotherContainer]}>
{(container, another) => (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
})
</Subscribe>
);
}
}
UPDATE: Or do it in this HOC way. After creating this:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import DefaultStore from "../store/DefaultStore";
const withUnstated = (
WrappedComponent,
Stores = [DefaultStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Then wrap your component like so:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
import AnotherContainer from './containers/another-container';
class Login extends Component {
constructor(props){
super(props)
}
render() {
const {LoginContainer: container} = this.props;
return (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
);
}
}
export default withUnstated(Login, [LoginContainer, AnotherContainer])
I try to test redux. I have component Main that redirects to TestRedux. TestRedux componenent make a dispatch and redirects to TestRedux2 when click on a button.
TestRedux and TestRedux2 are the same, I have made a copy/paste, just change values. TestRedux dispatch correctly, but testRedux2 trigger an error.
Do you know what is the cause of the problem ?
// TestRedux
import React from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { ADD_RES } from "../Constants/action-types";
import {addResa} from "../Actions/actions";
import { connect } from 'react-redux'
import Store from '../Store/store'
const mapStateToProps = state => ({ date: state.date })
export class TestRedux extends React.Component {
render() {
this.props.dispatch(addResa(1));
return (
<View>
<Button
onPress={() => { this.props.navigation.navigate('TestRedux2') }}
title='test'
/>
</View>
)
}
}
export default connect(mapStateToProps)(TestRedux)
// TestRedux2
import React from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { ADD_RES } from "../Constants/action-types";
import {addResa} from "../Actions/actions";
import { connect } from 'react-redux'
import Store from '../Store/store'
const mapStateToProps = state => ({ date: state.date })
export class TestRedux2 extends React.Component {
render() {
this.props.dispatch(addResa(2));
return (
<View>
<Button
onPress={() => { this.props.navigation.navigate('TestRedux') }}
title='test2'
/>
</View>
)
}
}
export default connect(mapStateToProps)(TestRedux2)
Store is:
import { createStore } from "redux"; // without redux persist
import { persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import resaReducer from "../Reducers/resaReducer";
const rootPersistConfig = {
key: 'root',
storage: storage
}
const Store = createStore(persistCombineReducers(rootPersistConfig, {resaReducer}))
export default Store;
````
Dispatch an action or pass null to connect. See if the issue continues.
export default connect(mapStateToProps)(TestRedux2)
to
export default connect(mapStateToProps, null)(TestRedux2)
I am trying to navigate to the first page after user logout!
and getting this error:
undefined is not a function (evaluating 'navigation.dispatch').
my action creator code is as follows:
import {NavigationActions} from 'react-navigation';
import {AsyncStorage} from 'react-native';
import {applyMiddleware as dispatch} from "redux";
export const userRemove = (navigation) => {
AsyncStorage.removeItem('user');
const navigateAction = NavigationActions.navigate({
routeName: 'First',
props: {}
});
navigation.dispatch(navigateAction);
};
I have called this action from sideBar of component. The component code is correct since that AsyncStorage.removeItem('user') is working correctly!
sorry for bad grammar. I can't write in english better than this error message here
my component code here:
import React, {Component} from 'react';
import {
Container,
Content,
Footer,
FooterTab,
Button,
Icon
} from 'native-base';
import {userRemove} from '../actions/sideBarAction';
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
class SideBar extends Component {
constructor(props) {
super(props);
}
logout() {
userRemove(this.props.navigation);
}
render() {
return (
<Container>
<Content>
...
</Content>
<Footer>
<FooterTab>
<Button onPress={this.logout.bind(this)}>
<Icon name="logout"/>
</Button>
</FooterTab>
</Footer>
</Container>
)
}
}
const mapStateToProps = state => {
return {
username: state.home.username,
password: state.home.password,
score: state.home.score,
}
};
const mapDispatchToProps = dispatch => {
return Object.assign({dispatch: dispatch},
bindActionCreators(userRemove, dispatch));
};
connect(mapStateToProps, mapDispatchToProps)(SideBar);
Is your component added to you main navigator? If not see if you can add.
You can also wrap your component with withNavigation function from react-navigation. That will make sure you have valid navigation prop.
import { withNavigation } from react-navigation;
...
connect(mapStateToProps, mapDispatchToProps)(withNavigation(SideBar));
Alternatively, you may pass this.props.navigation from parent component too.
I'm using React Router for a React Native app. Not sure what I'm missing here but I install react-router-native and require the history package, and setup a couple methods that just push a new route onto the stack but nothing happens. I console.log('clicked'); to check that it's firing and it is so not sure what's wrong.
import React, { Component } from 'react';
import { View } from 'react-native';
import Splash from './Splash';
import createHistory from 'history/createMemoryHistory';
const history = createHistory();
class SplashContainer extends Component {
goToLogin = () => {
history.push('/Login');
}
goToRegister = () => {
history.push('/SignUp');
}
render () {
console.log(history)
return (
<Splash
goToLogin={this.goToLogin}
goToRegister={this.goToRegister}
/>
);
}
}
export default SplashContainer;
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { Button } from 'native-base';
import { Link } from 'react-router-native';
import PropTypes from 'prop-types';
const Splash = (props) => {
console.log(props)
return (
<View style={styles.container}>
<Button light block onPress={props.goToLogin}>
<Text>Login</Text>
</Button>
<Button dark block bordered style={{marginTop: 10}} onPress={props.goToRegister}>
<Text>Register</Text>
</Button>
</View>
);
}
Splash.propTypes = {
goToLogin: PropTypes.func.isRequired,
goToRegister: PropTypes.func.isRequired
}
export default Splash;
I don't know your Router config, but your methods should be:
goToLogin = () => {
const { history } = this.props
history.push('/Login');
}
history will passed down via props of component inside Router's stack.
I'm playing with react-native / redux and am dispatching an action that is supposed to display a number yet an error gets thrown:
Unhandled JS Exception: Objects are not valid as a React child (found:
object with keys {type, payload}). If you meant to render a collection
of children, use an array instead or wrap the object using
createFragment(object) from the React add-ons. Check the render method
of Text.
createStore.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createLogger from 'redux-logger';
import numReducer from './reducers/numReducer';
const logger = createLogger();
export default (initialState = {}) => (
createStore(
combineReducers({
numbers: numReducer
}),
initialState,
applyMiddleware(logger)
)
);
App.js
import React from 'react';
import { Provider } from 'react-redux';
import HomeScreen from './components/HomeScreen';
import createStore from './createStore';
const store = createStore();
export default () => (
<Provider store={store}>
<HomeScreen />
</Provider>
);
numReducer.js
import { LIST_NUMBERS, PICK_NUMBER } from '../actions/actionTypes';
export default (state = [], action = {}) => {
switch (action.type) {
case LIST_NUMBERS:
return action.payload || [];
case PICK_NUMBER:
return action.payload;
default:
return state;
}
};
HomeScreen.js
import React from 'react';
import { View } from 'react-native';
import NavContainer from '../containers/NavContainer';
const HomeScreen = () => (
<View>
<NavContainer />
</View>
);
export default HomeScreen;
NavContainer.js
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { listNumbers, pickNumber } from '../actions/numberActions';
import Nav from '../components/Nav';
const mapStateToProps = state => ({
numbers: state.numbers
});
const mapDispatchToProps = dispatch => (
bindActionCreators({
listNumbers,
pickNumber
}, dispatch)
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Nav);
Nav.js
import React, { Component, PropTypes } from 'react';
import { View, Text } from 'react-native';
export default class Nav extends Component {
render() {
return (
<View>
<Text>FirstLine</Text>
<Text>SecondLind</Text>
<Text>Number: {this.props.pickNumber(3)}</Text>
</View>
);
}
}
Please advise what I am doing wrong. Thank you
You need to dispatch your action from inside one of your lifecycle methods or on some handler, and then use the (updated) props from your redux store in your component.
Example:
import React, { Component, PropTypes } from 'react';
import { View, Text } from 'react-native';
export default class Nav extends Component {
componentDidMount() {
this.props.pickNumber(3);
}
render() {
return (
<View>
<Text>FirstLine</Text>
<Text>SecondLind</Text>
<Text>Number: {this.props.numbers}</Text>
</View>
);
}
}