0

Where exactly should I deal with the problem of this component not loading with the desired state?

My render method causes the following error...

Uncaught TypeError: Cannot read property 'email' of undefined

...even though the JSON.stringify line shows me that the email property does (eventually) exist.

The console.log down in mapStateToProps confirms that state loads first without the any user property (thus causing the error).

Behold my naive attempt to resolve this in my constructor method. It's not working.

What is the right way to deal with this situation? Some conditional inside the render method? Tried that too but still no luck.

import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import * as actions from '../actions';

class Feature extends Component {

    constructor(props){
        super(props);
        this.state = {
            'auth': {
                'user':{
                    email:'',
                    id:''
                }
            }
        }
    }

    componentWillMount() {
        this.props.fetchMessage();        // puts the user object into state
    }

    render() {
        return (
            <div className="feature">
                Here is your feature
                {JSON.stringify(this.props.user , null, 2)}
                {this.props.user.email}
            </div>
        );
    }

}

function mapStateToProps(state) {
    console.log('state',state);
    return { user: state.auth.user }
}

export default connect(mapStateToProps, actions)(Feature);

/////////// action /////////

export function fetchMessage(){

    return function(dispatch){
        axios
            .get(ROOT_URL, {
                headers: {
                    authorization: localStorage.getItem('token')
                }
            })
            .then((response) => {
                dispatch({
                    type: FETCH_MESSAGE,
                    payload: response.data.user
                })
            })
    }

}

///////////////// reducer /////////////

var authReducer = (state={}, action) => {
    console.log('action.payload',action.payload);

    switch(action.type){
        case AUTH_USER: return      {...state, error: '', authenticated: true};
        case UNAUTH_USER: return    {...state, error: '', authenticated: false};
        case AUTH_ERROR: return     {...state, error: action.payload};
        case FETCH_MESSAGE: return  {...state, user: {
                                                        email: action.payload.email,
                                                        id: action.payload._id
                                                    }};
        default: return state;
    };
};
2
  • It would make it easier to help you if you could add your actions and reducers code. Commented Nov 22, 2016 at 3:46
  • done - but my JSON.stringify line tells me that the state is being updated correctly. It's just that split second between component loading, and state being updated that is causing me the problem Commented Nov 22, 2016 at 3:50

2 Answers 2

3

So here is what is happening.. You are making a server request for a user object and on success the received object is stored in your redux store. While performing such an action your component is rendered as follows:

  1. You have initiated the request to the server, which means currently there is no user in your store and so the component is rendered with this.props.user undefined.

  2. Your request is successful and the user object is stored in your store. When this happens react-redux will re-render your component with the user object and this.props.user is available in your component.

    During the first step since this.props.user is unavailable you are getting an error while accessing this.props.user.email. Although @jpdelatorre 's answer is pretty good, another thing you can do is simply add a check in your render method.

     render() {
         let user = this.props.user || {};
         ....
             {user.email}
         ....
     }
    
Sign up to request clarification or add additional context in comments.

1 Comment

Happy to help ! :)
2

Redux uses a global state called store that lives outside your component.

Inside your constructor you have a this.state = {...} statement. This is a local state that is only available to the Feature component.

connect from react-redux basically connects the info inside the store to your component props hence called mapStateToProps.

Edit your mapStateToProps to something like...

function mapStateToProps(state) {
   const { auth: { user = {} } = {}} = state;
   return {
      user
   }
}

What it does is try to extract the user property from the store and if it doesn't exist yet, set it to empty object. Your error is due to your mapStateToProps accessing the user property that doesn't exist yet. You might want to set a default value as your initialState to avoid this issue.

4 Comments

OK - your code worked, but that's some intense destructuring that will take me a few minutes to wrap my head around! Your last sentence, setting the initial state, seemed simpler. At which stage would I do that? Back where I use createStoreWithMiddleware?
Yes, you set the initial state in your create store.
Not in the first line of my authReducer?
Cool. Please marked your questions answered if it is indeed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.