Redux - Why is loginStatus undefined when component first starts rendering - javascript

I'm learning Redux and have come across an issue that I have not encountered before when using React without redux. I'm trying to display a piece of my state inside one of my components name loginStatus. The reducer I have setup this state with has an initial state but whenever I try and launch the application I get the console error:
Cannot read property 'loginStatus' of undefined
Here is my code:
Component
import React, {Component} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './userAuthActions';
class App extends Component {
constructor(props) {
super(props);
}
render() {
if(typeof(this.props.userAuthReducer) !== 'undefined') {
test = this.props.userAuthReducer.loginStatus;
console.log(test)
} else {
console.log("it's undefined")
}
return (
<div className={"popup-logins " + this.props.userAuthReducer.loginStatus}>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
userAuthReducer:state.userAuthReducer
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(authActions,dispatch);
};
export default connect(mapStateToProps,mapDispatchToProps)(App);
userAuthActions.js
export const loginUser = () => {
return {
type:'loginUser',
loggedIn:true
}
}
export const toggleRegLog = () => {
return {
type:'toggleRegLog'
}
}
userAuthReducer
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
combine reducers
import {combineReducers} from 'redux';
import userAuthReducer from './userAuthReducer';
function lastAction(state = null, action) {
return action;
}
export default combineReducers({
lastAction,userAuthReducer
});
What's strange is that I initially get a console.log of it's undefined" when I first start up the app and then immediately after I get the value "not-logged-in". I need to use this to hide/ show certain parts of my app if the user is logged in.
Normally if I use React without Redux I use this method all the time without any issues but can't understand what I might have done wrong here?
Thanks

You're not really assigning a value to newState in your reducer, so essentially you're returning undefined, which of course doesn't have a loginStatus property. Changing your reducer so to something like this will probably solve the problem:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
newState = Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
newState = Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
Object.assign returns a new object, applies the data from state and the last argument containing the loginStatus property and passes that to the newState variable, which gets returned at the end of the switch case.
Edit
This edit below makes it easier to reason about the logic in the reducer:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
if (action.loggedIn) {
return Object.assign(state, { loginStatus: "logged-in" })
}
return Object.assign(state, { loginStatus: "not-logged-in" })
default:
return state;
}
}
export default userAuthReducer;

Related

Redux reducers not being called when using dispatch() in Component

I'm pretty new to react/redux (and javascript in general) so please bear with me on my use of the terminology here...
I am trying to understand how Components and Reducers work so currently I am practicing on a small app that I mostly copied/pasted from a tutorial. The issue I am having is that I am trying to dispatch an action from my Component which alters the Redux state but I am not even seeing my console.log() messages inside my reducer function(s) when I dispatch an action from my Component.
This is what I currently have:
TwApp.js
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { TWAPP_USERDATA_AUTH_TOKEN } from '../Constants'
import { loginSuccess } from '../actions'
class TwApp extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleRefreshClick = this.handleRefreshClick.bind(this)
}
componentDidMount() {
console.log("TwApp componentDidMount")
this.props.loginSuccess() // This is where I want to dispatch an action
}
componentDidUpdate() {
}
handleChange() {
}
handleRefreshClick(e) {
e.preventDefault()
this.props.loginSuccess()
}
render() {
const { loggedIn } = this.props;
console.log("Rendering TwApp.")
if (!loggedIn) {
console.log("user not logged in. loggedIn = " + loggedIn)
}
else {
return (
<div>
<p>Success</p>
</div>
)
}
}
}
function mapStateToProps(state) {
// nothing for now
}
function mapDispatchToProps(dispatch) {
return {
loginSuccess: () => { dispatch(loginSuccess) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TwApp)
actions.js
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'
export function loginSuccess() {
return {
type: USER_LOGIN_SUCCESS,
}
}
reducers.js
// Contains a bunch of stuff that isn't being used yet
import { combineReducers } from 'redux'
import {
USER_LOGIN_SUCCESS, INVALIDATE_SUBREDDIT,
REQUEST_POSTS, RECEIVE_POSTS
} from './actions'
function reducer1(state = {}, action) {
console.log("reducer1: state =" + JSON.stringify(state) + ", action = " + JSON.stringify(action))
switch (action.type) {
case USER_LOGIN_SUCCESS:
console.log("Reducer USER_LOGIN_SUCCESS")
state.loggedIn = true
return state
case RECEIVE_POSTS:
case REQUEST_POSTS:
default:
return state
}
}
function reducer2(state = {}, action) {
console.log("reducer2: state =" + JSON.stringify(state) + ", action = " + JSON.stringify(action))
switch (action.type) {
case INVALIDATE_SUBREDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
default:
return state
}
}
const rootReducer = combineReducers({
reducer1,
reducer2
})
export default rootReducer
Neither reducer1's nor reducer2's console.log() message appears in console. When I call dispatch() from my TwApp component, do all reducers (reducer1 and reducer2) get called? Am I misunderstanding something?
Thanks
You are dispatching 'loginSuccess' which is a function, or by redux terms an action creator.
You should dispatch actions, which are plain objects, to your reducers.
What you want to do is dispatch the action that loginSuccess will create for you:
loginSuccess: () => { dispatch(loginSuccess()) }
comonentDidUpdate as you have it already:
componentDidMount() {
console.log("TwApp componentDidMount")
this.props.loginSuccess()
}
then remove your mapDispatchToProps function completely and export the following:
export default connect(mapStateToProps, {loginSuccess})(TwApp)

React Native: TypeError: undefined is not an object (evaluating '_this.props.data.map')

I wonder if React Native has a bug that needs fixing that gives the following error:
React Native: TypeError: undefined is not an object (evaluating
'_this.props.data.map')
I am pretty good at this and yet I cannot seem to resolve why I am getting this error when I put together this component:
import React, { Component } from "react";
import { View, Animated } from "react-native";
class Swipe extends Component {
renderCards() {
return this.props.data.map(item => {
return this.props.renderCard(item);
});
}
render() {
return <View>{this.renderCards()}</View>;
}
}
export default Swipe;
I have checked and double checked through various debugging practices that the problem is not with my action creator or reducer and after various refactors I got those working correctly.
I decided to do the above component from scratch whereas before I was reusing another component and yet I still get the above error.
I ask if it's a bug with RN because someone else posted a similar problem but they did not get the answer they needed.
It is not a scope issue with this because if I refactor it like so:
renderCards = () => {
return this.props.data.map(item => {
return this.props.renderCard(item);
});
};
It does absolutely nothing for me, same error message. The message saying is not an object is confusing too, it's an array and map() can only iterate through arrays, so not sure what not being an object has to do with it.
The above component is being called in this screen:
import React, { Component } from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import Swipe from "../components/Swipe";
class DeckScreen extends Component {
renderCard(job) {
return (
<Card title={job.title}>
<View style={styles.detailWrapper}>
<Text>{job.company}</Text>
<Text>{job.post_date}</Text>
</View>
<Text>
{job.description.replace(/<span>/g, "").replace(/<\/span>/g, "")}
</Text>
</Card>
);
}
render() {
return (
<View>
<Swipe data={this.props.jobs} renderCard={this.renderCard} />
</View>
);
}
}
const styles = {
detailWrapper: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 10
}
};
function mapStateToProps({ jobs }) {
return { jobs: jobs.listing };
}
export default connect(mapStateToProps)(DeckScreen);
This is what the action creator looks like:
import axios from "axios";
// import { Location } from "expo";
import qs from "qs";
import { FETCH_JOBS, LIKE_JOB } from "./types";
// import locationify from "../tools/locationify";
const JOB_ROOT_URL = "https://authenticjobs.com/api/?";
const JOB_QUERY_PARAMS = {
api_key: "5634cc46389d0d872723b8c46fba672c",
method: "aj.jobs.search",
perpage: "10",
format: "json"
};
const buildJobsUrl = () => {
const query = qs.stringify({ ...JOB_QUERY_PARAMS });
return `${JOB_ROOT_URL}${query}`;
};
export const fetchJobs = (region, callback) => async dispatch => {
try {
const url = buildJobsUrl();
let { data } = await axios.get(url);
dispatch({ type: FETCH_JOBS, payload: data });
callback();
} catch (e) {
console.log(e);
}
};
export const likeJob = job => {
return {
payload: job,
type: LIKE_JOB
};
};
and reducer:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
and the combineReducer is setup correctly as well:
import { combineReducers } from "redux";
import auth from "./auth_reducer";
import jobs from "./jobs_reducer";
import likedJobs from "./likes_reducer";
export default combineReducers({
auth,
jobs,
likedJobs
});
The listing: [] is based off the structure of the response I get back. When I console.log(data);, the actual data I care about is inside of listing property. So I set up the INITIAL_STATE to default listing to be an empty array with the intent to ensure I could map over the array and not worry about the case where I have not yet fetched the list of jobs. When I go to the API endpoint directly you can see it below:
I think the problem is simply that this.props.jobs is undefined. Your initial state is defined as { listing: [] }, however you mapStateToProps do { jobs: ... }.
Try changing initialState to { jobs: [] }, so that it always work on your first rendering.
I think your mapStateToProps should be:
mapStateToProps = (state) => {
return { jobs: listings.listing }
}
EDIT
Actually, it could be even better if you 'name' your state correctly in your reducer, like:
const INITIAL_STATE = { jobs: [] }
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
const jobs = action.payload.listings.listing
return { ...state, jobs };
default:
return state;
}
}
Then in your mapStateToProps:
mapStateToProps = ({ jobs }) => {
return { jobs }
}
The issue is in your reducer. Please refer the below changes:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
const { listings } = action.payload
return {...state, listing: listings.listing}
default:
return state;
}
}
Hope this will help.
function mapStateToProps({ jobs }) {
return { jobs: jobs.listing };
}
the above is making confusion for you try the below one
try to put
function mapStateToProps( state ) {
return { jobs: state.jobs.listing };
}
as you have defined your reducer as follow
export default combineReducers({
auth,
jobs,
likedJobs
});
jobs is your variable to access jobs reducer

React native with connect helper from redux doesn't re-render when state change?

I used reactJS and i know that a component that is wrapped with connect helper that listens to specific reducer when its reducer's state changes it causes the component to re-render.
I don't know why same procedure doesn't work for react-native, i tested my action creators as well as reducers and checked hundred percent that they return new state, And when i checked componentWillRecieveProps i found that the new state is returned correctly and the component doesn't re-render.
Reducer
const INITIAL = {
isSigned: null
}
export default (state = INITIAL, action) => {
switch(action.type){
case SIGNED_IN : return {...state, isSigned: true};
case LOGGED_OUT: return {...state, isSigned: false};
default: return state;
}
}
Component
import React, { Component } from 'react';
import { ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import * as actions from '../../actions';
class Loading extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.checkSigned();
switch(this.props.isSigned){
case null : return;
case false : this.props.navigation.navigate('Auth');
case true : this.props.navigation.navigate('App')
}
}
render(){
return (
<ActivityIndicator size="large" color="black" />
)
}
}
const mapStateToProps = ({signed}) => {
const {isSigned} = signed;
return {
isSigned
}
}
export default connect(mapStateToProps, actions)(Loading);
Actions
export const SIGNED_IN = 'SIGNED_IN';
export const LOGGED_OUT = 'LOGGED_OUT';
//Action Creators
export const checkSigned = () => async dispatch => {
let token = await AsyncStorage.getItem('fb_token');
if(token){
dispatch({type: SIGNED_IN})
}
dispatch({type: LOGGED_OUT})
}
You need to use bindActionCreators to dispatch your actions as props
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
const mapStateToProps = state => {
return {
isSigned: state.isSigned
}
}
export default connect(mapStateToProps, actions)(Loading);
// In actions, you need to fix action code
export const checkSigned = () => async dispatch => {
let token = await AsyncStorage.getItem('fb_token');
if(token){
dispatch({type: SIGNED_IN});
} else {
dispatch({type: LOGGED_OUT});
}
}
I think the problem is that you're running your state change logic in componentDidMount. componentDidMount doesn't run when your component re-renders, but componentDidUpdate does. Put your logic there.

React Component fails to re-render after update from Redux Store

Can anyone help me figure out why my Component is not updating after dispatch?
function mapStateToProps(state) {
const { isAuthenticated } = state
return { isAuthenticated }
}
class LoginForm extends Component {
handleSubmit(event) {
event.preventDefault();
const { dispatch } = this.props
const credentials = this.state
dispatch(attemptLogIn(credentials));
}
// Dispatch works properly when API validates Login:
// Redux Log:
// nextState:{ authReducer: {isAuthenticated: true}}
render() {
const { isAuthenticated } = this.props;
return (
<div>
{ isAuthenticated && <div> Authenticated: True </div> }
// Does not render even after dispatch
<form onSubmit={this.handleSubmit}>
{... Form Stuff ...}
</form>
</div>
)
}
export default withRouter(connect(mapStateToProps)(LoginForm))
Just simple conditional render from Redux store, I am expecting the extra div to show up to inform the user that he has authenticated, but It does not render.
This type of example of conditional rendering was used in the AsyncApp example during the Redux Async Tutorial, so I'm not sure why it doesn't work. My actions are dispatched, and reducers successfully update the state, passing it down to the connected component. Here are my reducers:
const initialState = { isAuthenticated: false}
const authReducer = (state = initialState, action) => {
switch(action.type){
case ('USER_AUTHENTICATED'): {
return Object.assign({}, state, {
isAuthenticated: true,
userPermissions: action.userInfo.userPermissions,
instanceType: action.userInfo.instanceType
}
)
}
case ('INVALID_CREDENTIALS'): {
return Object.assign({}, state, {
isAuthenticated:false
}
)
}
case ('LOG_OUT'): {
return initialState
}
default:
return state
}
}
const rootReducer = combineReducers({
authReducer,
routerReducer
})
export default rootReducer
Does anyone know why my Component does not re-render?
Change your mapStateToProps function to this.
function mapStateToProps(state) {
const { isAuthenticated } = state.authReducer;
return { isAuthenticated };
}

Write `state.tree` instead of `state.xxxReducer.tree` in `mapStateToProps` (react redux)

I have to write mapStateToProps like below
function mapStateToProps(state, ownProps) {
return {
node: ownProps.info? state.TreeNodeReducer.tree[ownProps.info.path] : {}
};
}
combine reducer:
import { combineReducers } from 'redux';
import TreeNodeReducer from './TreeNodeReducer'
const rootReducer = combineReducers({
TreeNodeReducer
});
export default rootReducer;
reducers/TreeNodeReducer.js
import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';
const initialState = {
open: false,
info: {}
}
class NodeModel {
constructor(path, type, right) {
this.name = path;
this.path = path;
this.type = type;
this.right = right;
}
}
let lastId = 0;
function getFileList() {
var testNodes = []
for (var i=0; i< 3; i++) {
testNodes.push(new NodeModel(lastId,'d', i.toString()))
lastId++;
}
return testNodes
}
const getNodes = (state, action) => {
var { path } = action
var tree = state.tree ? state.tree : {}
tree[path] = getFileList(path)
return {
...state,
tree:tree
};
};
export default function (state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
return { ...getNodes(state, action), open:true };
case GET_NODES:
return getNodes(state, action);
case CLOSE_NODE:
return {
...state,
open:false
};
default:
return state;
}
}
because state.TreeNodeReducer.tree is a global state, which hold all node info, I want to access it by state directly.State return by a reducer would wrap by the reducer's name, which is not convenient for simple project. Office doc don't provide the way.Any idea?
PS: I have to say I want to keep using combineReducers, I see some project not use it, directly pass reducer to store which can achieve my purpose but not good.
Achieving what you desire depends a bit on the state handled by the TreeNodeReducer.
If that reducer just handles the tree property, like this:
function treeNodeReducer(state = initialState, action) {
switch (action.type) {
case SOME_ACTION:
return Object.assign({}, state, { tree: action.tree, ... })
default:
return state
}
}
I'd say change the reducer to eliminate the tree property and merge it directly with the state, like this:
function treeNodeReducer(state = initialState, action) {
switch (action.type) {
case SOME_ACTION:
return Object.assign({}, state, action.tree)
default:
return state
}
}
This way we can access the tree object in mapStateToProps with state.treeNodeReducer.
But this is still not what we want. We want to rename treeNodeReducer to tree. There are two solutions for that:
Either:
import { combineReducers } from 'redux';
import TreeNodeReducer from './TreeNodeReducer'
const rootReducer = combineReducers({
tree: TreeNodeReducer
});
export default rootReducer;
Or:
import { combineReducers } from 'redux';
import tree from './TreeNodeReducer'
const rootReducer = combineReducers({
tree
});
export default rootReducer;
This way we can access the tree object in mapStateToProps with state.tree.

Categories

Resources