Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React / Redux - Cannot read property "XXX" of undefined

This is something I have been stuck on for over 2 weeks, so this question is my final chance at figuring this out. I hope anyone can help me (as the problem is most likely something small / something I have missed)


Using Node, Redux and React, I am returning a collection from my Mongo database. I am using react-redux "connect" to retrieve my data from my store As seen in my JSX below.

JSX:

import React from "react";
import {connect} from "react-redux"
import {fetchArticle} from "../../actions/articleActions"
var classNames = require('classnames');
import GlobalHero from '../modules/GlobalHero.jsx';

@connect((store) => {
    return {article: store.article.article, fetching: store.article.fetching};
})

export default class Article extends React.Component {

    // BEFORE COMPONENT RENDER (For Everyhing else)
    constructor() {
        super();
        //sets initial state
        this.state = {
            page: "Article"
        };
    }

    // BEFORE COMPONENT RENDER (For Ajax / Dispatcher Events): get article Title / Thumbnail rows based on this.props.indexLimit
    componentWillMount = () => {
        console.log(this.props)
        this.props.dispatch(fetchArticle(this.props.params.id))
    }

    // ON COMPONENT RENDER
    componentDidMount = () => {}

    render() {

        if (this.props.fetching) {
            return (
                <p>Loading...</p>
            );
        } else {
            return (
                <div>
                    <h1>{this.props.article.title}</h1>
                    <h2>{this.props.article.subTitle}</h2>

                </div>
            );
        }
    }
}

My problem:

So when I return "title" and "subTitle" in my JSX, it pulls everything through perfectly fine (see below):

  <h1>{this.props.article.title}</h1>
  <h2>{this.props.article.subTitle}</h2>

The data is also visible on my screen (see below):

enter image description here

But... As soon as I add:

  <h3>{this.props.article.body.section1.text}</h3>

My page will not load, and my console returns:

Cannot read property 'section1' of undefined

When I look at the state of my returned data in the console:

enter image description here

As you can see, it returns 'section1' in the console, so I must be calling my 'section1' wrong in my JSX?

I'm thinking the problem may be to do with the fact that 'section1' is nested further into my mongo db collection than 'title' or 'subTitle' is.

Below I will show you the rest of my routes for this page - I have looked endlessly online and cannot pinpoint my problem.

Action:

import axios from "axios";
//var resourceUrl = "http://localhost:7777/api/schools";

export function fetchArticle(id) {

    return function(dispatch) {
        dispatch({
            type: "FETCH_ARTICLE"
        })
        axios.get('/api/article', {
                params: {
                    id: id
                }
            })
            .then((response) => {
                dispatch({
                    type: "FETCH_ARTICLE_FULFILLED",
                    payload: response.data
                })
            })
            .catch((err) => {
                dispatch({
                    type: "FETCH_ARTICLE_REJECTED",
                    payload: err
                })
            })
    }
}

Reducer:

export default function reducer(state = {
    article: [],
    fetching: false,
    fetched: false,
    error: null,
}, action) {

    switch (action.type) {
        case "FETCH_ARTICLE":
            {
                return {
                    ...state,
                    fetching: true
                }
            }
        case "FETCH_ARTICLE_REJECTED":
            {
                return {
                    ...state,
                    fetching: false,
                    error: action.payload
                }
            }
        case "FETCH_ARTICLE_FULFILLED":
            {
                return {
                    ...state,
                    fetching: false,
                    fetched: true,
                    article: action.payload,
                }
            }
    }
    return state
}

Store:

import {
    applyMiddleware,
    createStore
} from "redux"

import logger from "redux-logger"
import thunk from "redux-thunk"
import promise from "redux-promise-middleware"

import reducer from "./reducers"

const middleware = applyMiddleware(promise(), thunk, logger())

export default createStore(reducer, middleware)

Node / Express Call:

app.get('/api/article', (req, res) => {

    var ObjectId = require('mongodb').ObjectID;
    var articles;

    db.collection('articles')
        .findOne({
            "_id": ObjectId("58c2a5bdf36d281631b3714a")
        })
        .then(result => {
            articles = result;
        }).then(() => {
            res.send(articles);
        }).catch(e => {
            console.error(e);
        });

});

The Record in my mongo DB collection:

{
    "_id": {
        "$oid": "58c2a5bdf36d281631b3714a"
    },
    "title": "EntertheBadJah",
    "subTitle": "Lorem ipsum dolor",
    "thmbNailImg": "",
    "headerImg": "",
    "body": {
        "section1": {
            "include": true,
            "text": "Lorem ipsum dolor sit amet, dico posse integre cum ut, praesent iudicabit tincidunt te sea, ea populo semper laoreet duo."
        },
        "section2": {
            "include": true,
            "text": "Lorem ipsum dolor sit amet, dico posse integre cum ut, praesent iudicabit tincidunt te sea, ea populo semper laoreet duo."
        },
        "bodyImg": {
            "include": true,
            "img": ""
        },
        "section3": {
            "include": true,
            "text": "Lorem ipsum dolor sit amet, dico posse integre cum ut, praesent iudicabit tincidunt te sea, ea populo semper laoreet duo."
        }
    },
    "links": {
        "recourse": {
            "include": false,
            "text": "Go watch their interview",
            "link": ""
        },
        "soundcloud": {
            "include": true,
            "link": "www.soundcloud.com/BadJah"
        },
        "spotify": {
            "include": false,
            "link": ""
        },
        "youtube": {
            "include": false,
            "link": ""
        },
        "twitter": {
            "include": false,
            "link": ""
        },
        "facebook": {
            "include": false,
            "link": ""
        },
        "instagram": {
            "include": false,
            "link": ""
        }
    },
    "keywords": "Badjah",
    "date": "",
    "author": "Aagentah",
    "dateAdded": "2017-06-01T00:00:00.000Z"
}

Any help or advice on this problem is appreciated - thank you in advance.

like image 782
holodan Avatar asked Dec 24 '22 19:12

holodan


1 Answers

React will rerender the page when its props (provided via Redux connect) change. Since you're only firing off the request to fetch the data in componentWillMount, by definition this will not have completed on the first render. However, that's no problem in the first case as this.props.article is guaranteed to be an object, [], so this.props.article.title will just be undefined. Once the request to the API returns, article is updated in the store and the page renders with the expected content.

However, since this.props.article is initially [], if you try to render this.props.article.body.section1.text, your code will throw an exception as observed since whilst article is an object, article.body is undefined. Having already thrown an error, the component will then fail to rerender when the article as actually updated in the store.

You need to add a guard to check the shape of this.props.article before trying to render subkeys, or else add a more complete default to the store for before the request returns with the actual article.

like image 151
richsilv Avatar answered Dec 28 '22 11:12

richsilv