react-native redux dispatch action from nested components - react-native

I am wondering how I can dispatch an action from n-th level nested components. This is wha I have got:
BodyContainer (contains connect, mapProps, mapDispatch, etc.) => Body
=Body (Component) where the actions are dispatched)
==Grid (Component)- state is passed as props from Body and some elements parts of the state are further passed on to the next component as props.
===Square (Component) - receives some part of the state as props.
Now, I'd like to dispatch an action from the Square component to change the state. I thought I'll just do a SquareContainer first but then how would it get the parts of the state from Grid?
See below the components (let me know if you need more information):
BodyContainer:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { listNumbers, pickNumber } from '../actions/numberActions';
import { populateRow, populateGrid } from '../actions/gridActions';
import Body from '../components/Body';
const mapStateToProps = state => ({
numbers: state.numbers,
grid: state.grid
});
const mapDispatchToProps = dispatch => (
bindActionCreators({
listNumbers,
pickNumber,
populateRow,
populateGrid
}, dispatch)
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Body);
Body Component
import React, { Component, PropTypes } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import Grid from './Grid';
import * as globalStyles from '../styles/global';
export default class Body extends Component {
componentWillMount() {
this.refresh();
}
refresh() {
this.props.populateGrid();
}
render() {
return (
<View style={styles.body}>
<Grid inGrid={this.props.grid} />
<View style={styles.buttonContainer}>
<Button
onPress={this.refresh.bind(this)}
title={'Regenerate the Grid'}
/>
</View>
</View>
);
}
}
Grid component
import React, { Component, PropTypes } from 'react';
import { View, StyleSheet } from 'react-native';
import Square from './Square';
import * as globalStyles from '../styles/global';
export default class Grid extends Component {
render() {
const row = [];
let i = 0;
this.props.inGrid.forEach((r) => {
r.forEach((c) => {
i++;
row.push(
<Square key={i} sqValue={c} />
);
});
});
const { grid } = styles;
return (
<View style={grid}>
{row}
</View>
);
}
}
Square Component
import React, { PropTypes } from 'react';
import { View, Text, StyleSheet, TouchableHighlight } from 'react-native';
import * as globalStyles from '../styles/global';
const Square = ({ sqValue }) => {
const { square, textStyle, squareActive } = styles;
return (
<TouchableHighlight
style={[square, sqValue[1] && squareActive]}
onPress={() => console.log(sqValue[0])}
>
<View>
<Text style={textStyle}>{sqValue[0]},{sqValue[1]}</Text>
</View>
</TouchableHighlight>
);
};
Edit: I've changed the Square component to a stateful one:
import React, { Component, PropTypes } from 'react';
import { View, Text, StyleSheet, TouchableHighlight } from 'react-native';
import * as globalStyles from '../styles/global';
export default class Square extends Component {
render() {
const { square, textStyle, squareActive } = styles;
const { sqValue } = this.props;
return (
<TouchableHighlight
style={[square, sqValue[1] && squareActive]}
onPress={() => console.log(sqValue[0])}
>
<View>
<Text style={textStyle}>{sqValue[0]},{sqValue[1]}</Text>
</View>
</TouchableHighlight>
);
}
}
I'd like to dispatch an action from onPress={}. Thank you

Related

react native header state to screen possible?

I have a header where is a input field. Is that possible to pass the value to my screen file ?
Header.js
import React, { useCallback } from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, TouchableOpacity, Dimensions, Platform, TextInput } from 'react-native';
import { useNavigation } from '#react-navigation/native';
import { Feather, AntDesign } from '#expo/vector-icons';
import Constants from 'expo-constants';
const width = Dimensions.get('window').width;
const headerHeight = 50;
const headerMessages = (text) => {
const navigation = useNavigation();
const [search, setSearch] = React.useState('');
const handleChangeInput = React.useCallback((e) => text(e));
const handleGoBack = useCallback(() => {
navigation.goBack();
});
return (
<View style={styles.container}>
<View style={styles.topHeader}>
<StatusBar color="#333" />
<Text style={styles.headerTitle}>Messages (0)</Text>
</View>
<View style={styles.searchContainer}>
<TextInput value={search} style={styles.searchInput} onChangeText={handleChangeInput} placeholder='Search Field' />
</View>
</View>
)
};
})
export default headerMessages;
Message.js
import * as React from 'react';
import { StyleSheet, Text, View, Pressable } from 'react-native';
const Messages = (props) => {
console.log(props.text);
return (
<View style={s.container}>
<Text>Messages</Text>
</View>
)
};
............................................................................................................................................................
If you want the 'search' state value in Message.js file, in that case initialize search in Message.js file and pass the setSearch hook as a callback function in the Header.js file.
And in the Header.js, get the state as props and set the value in textinput. In this way you will get the search value in the message.js file.

how to get state from child component

I need to get the state of the number from the component Numberplus
And display in the App component
app component:
import React from "react";
import { Text, View } from "react-native";
import Numberplus from "./Number";
function App() {
return (
<View>
<Text>{/* How to get Numberplus component State here */ Number}</Text>
<Numberplus />
</View>
);
}
export default App;
Numberplus component:
import { Button, Text, View } from "react-native";
function Numberplus() {
let [Number, setNamber] = useState(0);
return (
<View>
{/*<Text>{Number}</Text>*/}
<Button
onPress={() => {
setNamber(++Number);
}}
title="Plus"
/>
</View>
);
}
export default Numberplus;
See more details and display the output result
You can solve this problem either using redux or holding the state in your parent component. I can not explain whole redux here but here is how you can manage it with state.
App.js
import React from "react";
import { Text, View } from "react-native";
import Numberplus from "./Number";
function App() {
let [Number, setNumber] = useState(0);
return (
<View>
<Text>{/* How to get Numberplus component State here */ Number}</Text>
<Numberplus number={Number} onPress={() => {
setNumber(++Number);
}} />
</View>
);
}
export default App;
NumberPlus.js
import { Button, Text, View } from "react-native";
function Numberplus() {
return (
<View>
{/*<Text>{this.props.number}</Text>*/}
<Button
onPress={this.props.onPress}
title="Plus"
/>
</View>
);
}
export default Numberplus;

how to connect react with redux?

so I am trying to learn about react-redux using react-native and I want to make a page where I can input a number and press login. when I press login, the page will alert me the number I input and saved into the store I created with redux.
can anyone please tell me what i'm doing wrong and what should I add or do to make it work?
below is my testing page
import React, {Component} from 'react';
import {View, TextInput, TouchableOpacity, Text} from 'react-native';
import {connect} from 'react-redux';
import actions from '../Redux/Action';
class tes extends Component{
constructor(props){
super(props)
}
render(){
return(
<View>
<TextInput placeholder="phone number"
keyboardType="number-pad"/>
<TouchableOpacity onPress={this.props.onLogin}>
<Text>login</Text>
</TouchableOpacity>
</View>
)
}
}
mapStateToProps = state => {
return {
number: state.phoneNumber
}
}
mapDispatchToProps = dispatch => {
return {
onLogin: (number) => {
dispatch(actions.setLoginNumber(number))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(tes);
this is my store class
import {createStore} from 'redux';
import reducer from './Reducer';
export default createStore(reducer)
here is my reducer class
const reducer = (state = {
phoneNumber: '',
},action) => {
switch(action.type) {
case "LOGIN":
state = {
phoneNumber: action.payload
}
break;
}
return state;
}
export default reducer;
{/* and this one my action class */}
export default function setLoginNumber(number) {
return{
type: "LOGIN",
payload: number
};
}
thanks in advance..
I think your not passing parameter number to onLogin function and you will need local state variable to hold the value. The code should be like this
import React, {Component} from 'react';
import {View, TextInput, TouchableOpacity, Text} from 'react-native';
import {connect} from 'react-redux';
import actions from '../Redux/Action';
class tes extends Component{
constructor(props){
super(props)
this.state = {
number: 0,
};
}
render(){
return(
<View>
<TextInput placeholder="phone number"
onChangeText={inputNumber => {
this.setState({ number: inputNumber })
}}
keyboardType="number-pad"/>
<TouchableOpacity onPress={() => {this.props.onLogin(this.state.number) }}>
<Text>login</Text>
</TouchableOpacity>
</View>
)
}
mapStateToProps = state => {
return {
number: state.phoneNumber
}
}
mapDispatchToProps = dispatch => {
return {
onLogin: (number) => {
dispatch(actions.setLoginNumber(number))
}
}
}
Answer for your second question -
You haven't passed created store to provider component of react-redux like below example
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
export default class Root extends Component {
constructor() {
super();
}
render() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
}
Hope it helps.

Cannot read property 'navigate' of undefined in react-navigation

I have used import { StackNavigator } from 'react-navigation'; in my Router.js
import { StackNavigator } from 'react-navigation';
import LoginForm from './components/LoginForm';
import EmployeeList from './components/EmployeeList';
import EmployeeCreate from './components/EmployeeCreate';
const RouterComponent = StackNavigator(
{
LoginForm: {
screen: LoginForm,
navigationOptions: {
title: 'Please Login'
}
},
EmployeeList: {
screen: EmployeeList,
},
EmployeeCreate: {
screen: EmployeeCreate,
navigationOptions: {
title: 'Create Employee'
}
}
},
{
initialRouteName: 'LoginForm',
}
);
export default RouterComponent;
Of course i use it in my App.js
import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './src/reducers';
import Router from './src/Router';
export default class App extends React.Component {
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<Router />
</Provider>
);
}
}
And i can use this.props.navigation in my LoginForm.js like this function:
onButtonPress() {
const { email, password, navigation } = this.props;
this.props.loginUser({ email, password, navigation });
}
I pass navigation to my Action file , i can use it to navigate another screen , like this:
const loginUserSuccess = (dispatch, user, navigation) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user
});
//navigation is from LoginForm.js , navigate to EmployeeList is working
navigation.navigate('EmployeeList');
};
Now i try to use this.props.navigation.navigate in my ListItem.js
My ListItem is under EmployeeList.js
Here is my EmployeeList.js
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { View, Text, Button, FlatList } from 'react-native';
import { employeesFetch } from '../actions';
import ListItem from './ListItem';
class EmployeeList extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'EmployeeList',
headerLeft: null,
headerRight: <Button title="Add" onPress={() => navigation.navigate('EmployeeCreate')} />,
});
componentWillMount() {
this.props.employeesFetch();
}
// Using ListItem over here
renderRow(employee) {
return <ListItem employee={employee} />;
}
render() {
console.log(this.props);
return (
<FlatList
data={this.props.employees}
renderItem={this.renderRow}
keyExtractor={employee => employee.uid}
/>
);
}
}
const mapStateToProps = state => {
const employees = _.map(state.employees, (val, uid) => {
return { ...val, uid };
});
return { employees };
};
export default connect(mapStateToProps, { employeesFetch })(EmployeeList);
Here is my problem use this.props.navigation.navigate in ListItem.js
import React, { Component } from 'react';
import { Text, View, TouchableWithoutFeedback } from 'react-native';
import { CardSection } from './common';
class ListItem extends Component {
onRowPress() {
this.props.navigation.navigate('EmployeeCreate');
}
render() {
const { item } = this.props.employee;
return (
<TouchableWithoutFeedback onPress={this.onRowPress.bind(this)}>
<View>
<CardSection>
<Text style={styles.titleSytle}>
{item.name}
</Text>
</CardSection>
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = {
titleSytle: {
fontSize: 18,
paddingLeft: 15
}
};
export default ListItem;
I can use this.props.navigation in my LoginForm.js , i can't figure it out why i use it in ListItem.js navigate is undefined ?
Any help would be appreciated. Thanks in advance.
in file EmployeeList.js pass navigation as prop to ListItem.
renderRow(employee) {
return <ListItem employee={employee} navigation={this.props.navigation} />;
}
Now you should be able to access navigation using this.props.navigation inside ListItem.js.
Just an observation, never bind methods to context inside the render
function as it is called repeatedly and a new instance will be created
each time. Change your ListItem.js as below.
class ListItem extends Component {
constructor(props) {
super(props);
this.onRowPress = this.onRowPress.bind(this); // here we bind it
}
onRowPress() {
this.props.navigation && this.props.navigation.navigate('EmployeeCreate');
}
render() {
const { item } = this.props.employee;
return (
<TouchableWithoutFeedback onPress={this.onRowPress}>
<View>
<CardSection>
<Text style={styles.titleSytle}>
{item.name}
</Text>
</CardSection>
</View>
</TouchableWithoutFeedback>
);
}
}
Use withNavigation. In your ListItem.js file add import { withNavigation } from ‘react-navigation’; and replace export default ListItem; with export default withNavigation(ListItem);
Here is what I did (using latest react native with ES6):
Refactored the class code from this:
export default class MyComponent extends React.Component {
// content & code
}
To look like this:
import { withNavigation } from 'react-navigation';
class MyComponent extends React.Component {
// content & code
}
export default withNavigation(MyComponent);
According to the docs (withNavigation):
"withNavigation is a higher order component which passes the navigation prop into a wrapped component."

'invariant violation :element type is invalid' issue in react-native

I am using redux form in my register component and trying to pass it into redux-store.
app.js
import React, { Component } from 'react';
import WelcomePage from './components/welcomePage';
import Register from './components/register';
import { StackNavigator } from 'react-navigation'
import {
Platform,
StyleSheet,
Text,
View
} from 'react-native';
import { Provider,connect } from 'react-redux';
import store from './store/store';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
type Props = {};
const mapStateToProps=state => {
return state;
}
const mapDispatchToProps=dispatch => {
return {}
}
const handleSubmit=values=> {
console.log(values);
}
const Navigationapp=StackNavigator({welcome:{screen:WelcomePage},register:{screen:props=><Register {...props} handleSubmit={handleSubmit} />}});
const Container = connect(mapStateToProps,mapDispatchToProps)(Navigationapp);
export default class App extends Component{
render(){
return (<Provider store={store}>
<Container/>
</Provider>)
}
};
register.js
import React,{ Component } from 'react';
import { Field,reduxForm } from 'redux-form';
import { Text,Input } from 'react-native-elements';
import { View,Button } from 'react-native';
const renderField=({label,keyboardType,name}) => {
return(
<View style={{flexDirection:'row',height:50,alignItems:'center' }}>
<Text>
<h4>{label}</h4>
</Text>
<Input />
</View>
)
}
const RegisterForm=props => {
const {handleSubmit}=props;
return(
<View style={{flex:1,flexDirection:'column',margin:40,justifyContent:'flex-start'}}>
<Field label="Username" component={renderField} name="username" />
<Button title='SUBMIT' onPress={handleSubmit} />
</View>
)
}
const Register=reduxForm({
form:'register',
})(RegisterForm);
export default Register;
store.js
import { createStore,combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
const rootReducer=combineReducers({
form:formReducer
})
const store=createStore(rootReducer);
export default store;
when I navigate into the register component it shows following error.
I still failed to confirm whether the issue is in my register component or in my app.js.Please help me to solve this issue.
The Input component doesn't exist in react-native-elements, just remove it from the import statement and from renderField :
import React,{ Component } from 'react';
import { Field,reduxForm } from 'redux-form';
import { Text } from 'react-native-elements'; // Removed 'Input' import
import { View,Button } from 'react-native';
const renderField=({label,keyboardType,name}) => {
return(
<View style={{flexDirection:'row',height:50,alignItems:'center' }}>
<Text>
<h4>{label}</h4>
</Text> // Removed <Input />
</View>
)
}
Edit: You may want to use <FormInput /> instead