I am new to react-native and I am trying to implement a simple sign up functionality using react-redux. For some reasons , mapping the state to props in connect is not working.
Below is my code :
SignUp.js ( Component )
import React from 'react';
import { View, Text , TouchableOpacity , TextInput } from 'react-native';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as signUpActions from "../actions/SignUpActions";
class SignUp extends React.Component {
constructor(){
super();
this.state = {
name : '',
password : '',
};
}
saveUser(){
let user = {};
user.name = this.state.name;
user.password = this.state.password;
this.props.registerUser(user);
}
static navigationOptions = {
title : 'Sign Up',
};
render(){
return (
<View>
<TextInput
placeholder="Username"
onChangeText={(text) => this.setState({name : text})}
/>
<TextInput
placeholder="Password"
onChangeText={(text) => this.setState({password : text})}
/>
<TouchableOpacity onPress = {() => this.saveUser()} >
<Text>DONE</Text>
</TouchableOpacity>
</View>
);
}
}
export default connect(
state => ({
user : state.user
}),
dispatch => bindActionCreators(signUpActions, dispatch)
)(SignUp);
SignUpAction.js
function storeUser(user) {
return {
type : 'REGISTER_USER',
payload : user,
};
};
export function registerUser(user) {
return function (dispatch, getState) {
fetch(<the-url>)
.then((response) => {return response.json()})
.then((responseData) => dispatch(storeUser(responseData)))
.catch((err) => console.log(err));
};
};
SignUpReducer.js
const initialState = {
data : {},
};
export default function signUpReducer(state = initialState, action) {
console.log(action.payload)
//This gives {id:26 , name : "xyz" ,password:"pass"}
switch (action.type) {
case 'REGISTER_USER' :
return {
...state ,
user : action.payload
}
default :
return state;
}
}
This my root reducer
export default function getRootReducer(navReducer) {
return combineReducers({
nav: navReducer,
signUpReducer : signUpReducer,
});
}
The register user function is being called. And the fetch request is also successfully executed over a network. It returns the same user object back after storing it in a database. It dispatches to the storeUser function as well. The reducer is getting called as well.
But , for some reasons , the state is not mapped to the props inside the connect. this.props.user returns undefined.
I must be doing something wrong in this but I am not able to figure it out. Based on what I have seen till now when we dispatch any action using bindActionCreators the result from reducer needs to be mapped to component's props using connect. Please correct me if wrong.
Please help me with this issue.
From your store defination,
return combineReducers({
nav: navReducer,
signUpReducer : signUpReducer,
});
You defined the key signUpReducer for your SignUp component state.
In order to access the state of this component,you should use this key followed by the state name.
The correct way to access user is :
export default connect(
state => ({
user : state.signUpReducer.user
})
//use signUpReducer key
FOR ME ALSO THIS IS WORKING componentWillReceiveProps(nextProps)
componentWillReceiveProps(nextProps) {
console.log();
this.setState({propUser : nextProps.user})
}
Related
I'm learning to use Redux to fetch user's info in React Native but currentUser is null error keeps popping up, I think I set things up properly though. Can someone help me in that?
Here's my code:
import React from 'react'
import { View, Text, Button } from 'react-native';
import { connect } from 'react-redux';
function Profile(props) {
const { currentUser } = props;
console.log({currentUser})
return (
<View>
<Text>{currentUser.name}</Text>
</View>
);
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
})
export default connect(mapStateToProps, null)(Profile);
And here's how I dispatch data using Redux:
export function fetchUser() {
return ((dispatch) => {
firebase.firestore()
.collection("Users")
.doc(firebase.auth().currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
dispatch({ type: USER_STATE_CHANGE, currentUser: snapshot.data() })
}
else {
console.log('does not exist')
}
})
})
}
I think that this is caused because you are trying to render the Profile component even when currentUser would be null which would fail because you can't access a property off of null. This can be controlled within the Profile component:
export default function Profile(props) {
const { currentUser } = props;
if (!currentUser) {
return (
<View>
<Text>Loading user...</Text>
</View>
);
}
return (
<View>
<Text>{currentUser.name}</Text>
</View>
);
}
I am new to react native and redux.
In this file friends.js, I have already made it so that the app adds a friend when someone taps the "Add Friend" button. I am now also trying to make a form that adds a new name to a list of names. Here is the form:
import React from 'react';
import { StyleSheet, Text, View, Button, TextInput, KeyboardAvoidingView } from 'react-native';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import {addFriend} from './FriendActions';
import {addFriendFromForm} from './FriendActions';
class Friends extends React.Component {
constructor(){
super();
this.formFieldValue = "";
}
render() {
return (
<KeyboardAvoidingView behavior="padding" enabled>
<Text>Add friends here!</Text>
{this.props.friends.possible.map((friend,index) => (
<Button
title={`Add ${friend}`}
key={friend}
onPress = { () =>
this.props.addFriend(index)
}
/ >
)
)}
<Button
title="Back to Home"
onPress={() => this.props.navigation.navigate('Home')}
/>
<TextInput
style={{height: 40, width: 200}}
placeholder="Type in a friend's name"
onChangeText={(text) => {
this.formFieldValue = text;
console.log(this.formFieldValue);
}
}
/>
<Button
title="Add Friend From Form"
onPress={() => this.props.addFriendFromForm(this.formFieldValue)}
/ >
</KeyboardAvoidingView>
);
}
}
const mapStateToProps = (state) => {
const friends = state;
console.log("friends", JSON.stringify(friends));
return friends
};
const mapDispatchToProps = dispatch => (
bindActionCreators({
addFriend,
addFriendFromForm
}, dispatch)
);
export default connect(mapStateToProps, mapDispatchToProps)(Friends);
Here is the action that the form triggers:
export const addFriend = friendIndex => ({
type: 'ADD_FRIEND',
payload: friendIndex
})
export const addFriendFromForm = friendNameFromForm => ({
type: 'ADD_FRIEND',
payload: friendNameFromForm
})
And here is the reducer that calls the actions . (WHERE I THINK THE PROBLEM IS):
import {combineReducers} from 'redux';
const INITIAL_STATE = {
current: [],
possible: [
'Allie',
'Gator',
'Lizzie',
'Reptar'
]
};
const friendReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case 'ADD_FRIEND':
const {
current,
possible
} = state;
const addedFriend = possible.splice(action.payload, 1);
current.push(addedFriend);
const newState = {current, possible};
return newState;
case 'ADD_FRIEND_FROM_FORM':
const {
currents,
possibles
} = state;
currents.push(action.payload);
const newState2 = {current: currents, possible: possibles};
return newState2;
default:
return state;
}
};
export default combineReducers({
friends: friendReducer
});
As I mentioned, pressing the button to automatically add a name from the initial state works. However, when adding a name through the form, it seems that the form will just add allie to possible friends instead of the name typed into the form because the console.log in friends.js shows this: friends {"friends":{"current":[["Allie"]],"possible":["Gator","Lizzie","Reptar"]}}.
How do I get the form to work?
I used this.formFieldValue in friends.js as it seems like overkill to change the state whenever the form field text changes when we really only need it when the submit button is pressed. Is this the proper way to go about sending the form data to the action?
While it may seem liike I'm asking 2 questions, I'm pretty sure it's one answer that will tie them both togethor. Thanks!
The issue might be that you're directly mutating the state with push, try using array spread instead:
const friendReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case 'ADD_FRIEND':
const {
current,
possible
} = state;
const addedFriend = possible.splice(action.payload, 1);
current.push(addedFriend);
const newState = {current, possible};
return newState;
case 'ADD_FRIEND_FROM_FORM':
return {...state, current: [...state.current, action.payload]}
default:
return state;
}
};
Also it's a good idea to be consistent with the property names, it seems you're using current and currents interchangeably.
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.
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
}
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.