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

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):
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:
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.

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.

Try something like this. Could be that in the first render cycles its just not present yet.
checkRender(){
typeof this.props.article.body === 'undefined'
? return null
: return this.props.article.body.section1.text
}
Then use it ::>
<h3>{checkRender}</h3>

I can definitely recommend to add Flow type checks to your codebase (http://flow.org). It can be added on a file-per-file basis but can help significantly in debugging these kind of problems.

Related

What causes this "maximum call stack size exceeded" error?

I am working on a Vue 3 and Bootstrap 5 app. I needed a date-picker and I choose Vue 3 Datepicker.
In components\Ui\Datepicker.vue I have:
<template>
<datepicker
#selected="handleSelect"
v-model="dateSelected"
:upper-limit="picked_to"
:lower-limit="picked_from"
class="datepicker text-center" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const dateSelected = ref(new Date());
return {dateSelected}
},
methods: {
handleSelect() {
this.$emit('setDate')
}
}
}
</script>
In components\Ui\Navigation.vue I have:
import Datepicker from './Datepicker'
export default {
inject: ['$apiBaseUrl'],
name: 'Navigation',
components: {
Datepicker,
},
data() {
return {
// more code
}
},
methods: {
setDate() {
this.$emit('setDate');
}
},
}
In components\Content.vue I have:
<template>
<div class="main">
<div class="d-sm-flex>
<h1>{{ title }}</h1>
<Navigation
#setDate='setDate'
/>
</div>
<div class="content">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
</div>
</div>
</template>
<script>
import Navigation from './Ui/Navigation'
export default {
inject: ['$apiBaseUrl'],
name: 'Content',
components: {
Navigation,
},
props: {
title: String,
},
emits: ['setDate'],
data() {
return {
headers: {
'content-type': 'application/json',
'Accept': 'application/json'
},
from: '',
to: '',
}
},
methods: {
sendData() {
this.axios.post(`${this.$apiBaseUrl}/submit`, this.fields, {options: this.headers}).then((response) => {
if (response.data.code == 200) {
this.isReport = true;
}
}).catch((errors) => {
this.errors = errors.response.data.errors;
});
}
},
setDate() {
console.log('Date');
},
}
}
</script>
The problem
Although I select a date from the datepicker, the setDate() method is not executed. The Chrome console shows instead:
Maximum call stack size exceeded
Where is my mistake?
As comments have mentioned previously, the error usually occurs when having an infinite loop.
As Kissu pointed out this can easily happen if you have an event that is emitted to a parent component which then changes data that is passed as props to a child component which then triggers an event to the parent and so on.
In the code you showed I can't really find any loops, they might be in the parts you omitted.
Edit:
The infinite loop could actually be caused by the naming of your component.
You're using <datepicker ...> inside Datepicker.vue without registering the Datepicker of vue3-datepicker there explicitly.
That probably causes vue to recursively try to mount the component in itself causing the maximum stack trace error, but that's just a guess.
But there's a couple issues in your code still.
First off:
<template>
<datepicker
#selected="handleSelect" // <-- according to the docs there are no events that the datepicker emits
v-model="dateSelected"
:upper-limit="picked_to" // <-- where does the picked_to value come from?
:lower-limit="picked_from" // <-- where does the picked_from value come from?
class="datepicker text-center" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const dateSelected = ref(new Date());
return {dateSelected}
},
// do not mix options & composition api
methods: {
handleSelect() {
this.$emit('setDate')
}
}
}
</script>
You're mixing options and composition API, which is never a good idea.
There's also a couple values that seemingly come from nowhere and you're listening to a #selected event from the datepicker, which according to the docs doesn't exist, so you'll have to watch for changes in the selected date yourself.
The same component in the composition API would look like this:
<template>
<Datepicker v-model="initialDate" class="datepicker text-center" />
</template>
<script>
import Datepicker from "vue3-datepicker";
import { ref, watch } from "vue";
export default {
components: { Datepicker },
setup(props, { emit }) {
const initialDate = ref(new Date());
watch(initialDate, (newDate) => {
emit("setDate", newDate);
});
return { initialDate };
},
};
</script>
I tried to recreate the example you gave in a sandbox and I do not encounter any infinite loop issues.
You might wanna check it out and compare with your code and possibly fix all the other issues first and see if that helps your situation :)

Need help to pass data from page to dynamic page

I'm creating a website to showcase products using NextJs. I need to pass data from a page to a dynamic page. All the data is a json array of objects located in a data folder inside the project. I want to list all objects in pages/rolls/index.jsx, and when an objects corresponding "Learn More" Link button is clicked, takes me to a more detailed version of the page. I can't make it work using next/link and useRouter, so here's what I did.
My project structure is:
- data
- rollData.json
- pages
- rolls
- [rolldetail].jsx
- index.jsx
// rollData.json
[
{
"name": "8\" x 0.080\" x 300'",
"price": 300,
"product_id": "PL8080300",
"image": "/8flat.webp",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
},
{
"name": "12\" x 0.080\" x 300'",
"price": 450,
"product_id": "PL12080300",
"image": "/12flat.webp",
"description": "Ac turpis egestas sed tempus urna et pharetra"
}
]
// rolls.jsx
import rollsdata from '../../data/rollData.json'
const Rolls = () => {
return (
<main>
{rolls.map((roll) => {
return (
<div key={roll.product_id}>
<h2>{roll.name}</h2>
<h3>
Description
</h3>
<p>{`${roll.description.slice(0, 163)}.`}</p>
<div>
<span>Price</span>
<span>{`$${roll.price.toFixed(
2
)}`}</span>
</div>
<Link href={`/rolls/${encodeURIComponent(roll.product_id)}`}>
<a>click</a>
</Link>
</div>
)
})}
</main>
)
}
// [rollDetail].js
import { useRouter } from 'next/router'
import rolls from '../../data/rolls.json'
const RollDetail = () => {
const router = useRouter()
const roll = rolls[router.query.rolldetail]
console.log(roll)
return (
<div>
<h2>Roll detail</h2>
<p>{roll.name}</p>
<p>{roll.description}</p>
</div>
)
}
export default RollDetail
And it gives me the error:
Uncaught TypeError: roll is undefined
I don't understand how to use router to display a specific roll object, can someone tell me what I'm doing wrong and how to achieve it please? Should I be using getStaticProps and getStaticRoute?
Actually by default all the routes either they are dynamic or static will be statically pre-rendered during build time of app, unless you use getStaticProps, getInitialProps or getServerSideProps...
But during development all the pages pre-render every time you hit the request to specific url, no matter what you have used...
Now lets back to actual question, your dynamic pages will be rendered twice first time you are getting undefined as your url parameters but on second render your url parameters will be availble, so you can simply put checks to check either url parameters are undefined or not...
This is happening due to some reason, checkout this link => click

Why does this image not appear in the browser even after the state being loaded?

I'm trying to render a profile picture with an "src" link provided by the Twitch API. What I expected to happen was that the image renders correctly after the brief "Loading..." message, What I got was the loading message appearing for a brief second but after that there was no image, according to the browser all that got rendered was a <img> with no src.
I am pretty new to React and Next and after a day troubleshooting I still do not know why this doesn't work. I have read the docs pretty thoroughly and browsed through countless questions here and I still can't figure out what's wrong
This is the component:
export class twitchInfoCard extends Component {
constructor(props) {
super(props);
this.state = {
isFetching: true,
pfp: null,
};
}
componentDidMount() {
this.setState({ ...this.state, isFetching: true });
twitchApi.userQ('insomniac').then(r => {
r = r.json().then(result => {
this.setState({ pfp: result.data[0].thumbnail_url, isFetching: false });
console.log(result.data[0])
});
});
}
render() {
return (
<div className="profile-info rounded-md flex">
{this.state.isFetching ? 'Loading' : <img src={this.state.pfp} />}
</div>
)
}
}
This is the API response:
{
"id": "232672264",
"login": "insomniac",
"display_name": "Insomniac",
"type": "",
"broadcaster_type": "partner",
"description": "Wide Awake Since '93",
"profile_image_url": "https://static-cdn.jtvnw.net/jtv_user_pictures/be6b7ece-3c44-4100-8744-3578d112c862-profile_image-300x300.png",
"offline_image_url": "https://static-cdn.jtvnw.net/jtv_user_pictures/91e4f13e-ffcb-4889-8632-5df9392181d0-channel_offline_image-1920x1080.png",
"view_count": 152550798,
"created_at": "2018-06-19T20:47:21.29642Z"
}
Based on the API response, I dont see any name with thumbnail_url, If you want to render image, you may setState the pfp to profile_image_url or offline_image_url once the fetching is successful.

TypeError: variable is undefined despite being able to log the variable correctly

I'm trying to get a simple list of lessons contained in a course from an endpoint.
If I try console.log(this.state.course.lessons) an array contained 3 items is displayed.
However,if I try console.log(this.state.course.lessons.map(//function) I keep getting
TypeError: this.state.course.lessons is undefined
How can I map a function to the lessons array so I can render them as a list.
component
import React, { Component } from 'react'
export class CourseDetail extends Component {
constructor(props) {
super(props);
this.state = {
course: []
};
}
componentDidMount() {
fetch(`http://127.0.0.1:8000/api/courses/${this.props.match.params.id}`)
.then(res => res.json())
.then((course) => {
this.setState({
course: course,
});
console.log(this.state.course.lessons)
});
}
render() {
return (
<div>
{this.state.course.lessons.map((lesson)=>(console.log(lesson)))}
<h1>{this.state.course.title}</h1>
</div>
)
}
}
export default CourseDetail
json returned from end point
{
"id": 1,
"lessons": [
1,
2,
3
],
"owner": 1,
"rating": 0,
"title": "Course 1",
"description": "course 1 desc",
"pub_date": "2019-11-23",
"is_live": false,
"category": 1
}
Most obvious solution would be just to give the object a default state if you want to access it:
constructor(props) {
super(props);
this.state = {
course: {
lessons: []
}
};
}
The problem on your code is the life cycles (Mount-> Render-> DidMount), and in this render, u have not fetch the data yet.
You can try this:
render() {
if (!this.state.course.lessons) return null //this line
return (
<div>
{this.state.course.lessons.map((lesson)=>(console.log(lesson)))}
<h1>{this.state.course.title}</h1>
</div>
)
}

Unable to display API call result to WebPage

I have react App.js page from where i am calling Django Rest API and i am getting response as an array now this array i have nested components and i want that nested component to be listed in my code.
If i can showcase single record given by single person name when i try to do with more than one i am getting following error.
Warning: Each child in an array or iterator should have a unique "key" prop.
Now if i change API URL as below
https://e2isaop.rokuapp.com/api/perns/1
I can able to view data in HTML but when it comes to all persons it fails.
I am sorry i am new react not sure how to iterate over sub array of result.
Kindly guide me here for best practice for this.
Here is API Response in JSON
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"uri": "/api/Persons/1",
"PersonId": 1,
"PersonName": "Nirav Joshi",
"Person_Image": "https://ja.amazonaws.com/media/persons/None/51e1257926f3cb184089c41fa54b8f8e1b65a98f1e35d39e55f2b6b335e83cf4.jpg",
"Person_sex": "M",
"Person_BDate": "2019-04-19",
"Person_CDate": "2019-04-23"
},
{
"uri": "/api/Persons/2",
"PersonId": 2,
"PersonName": "New Joshi",
"Person_Image": "https://ja.amazonaws.com/media/persons/None/cc08baaad2ccc918bc87e14cac01032bade23a0733b4e313088d61ee78d77d64.jpg",
"Person_sex": "F",
"Person_BDate": "2011-11-21",
"Person_CDate": "2019-04-27"
},
]
}
Here is react App.js code.
import React from "react";
import ReactDOM from "react-dom";
import Persons from "./Persons";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
persons: []
};
}
componentDidMount() {
fetch("https://e2isen.okuapp.com/api/psons/")
.then(response => response.json())
.then(data => {
let apipersons;
if (data.isNull) {
apipersons = [];
} else {
apipersons = [data];
console.log(apipersons);
}
this.setState({ persons: apipersons });
});
}
render() {
return (
<div>
<h1>Welcome to PersonAPI</h1>
<div>
{this.state.persons.map(pers => {
return (
<Persons
PersonName={pers.PersonName}
key={pers.PersonId}
Person_Image={pers.Person_Image}
Person_BDate={pers.Person_BDate}
Person_sex={pers.Person_sex}
/>
);
})}
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
It should give me result for Four person with their details
PersonName
PersonImage
PersonBdate
PersonSex
You should do :
// apipersons = [data];
apipersons = data.results

Categories

Resources