Unable to access nested object in React - javascript

I am trying to access single elements within an object and I keep getting an error.
Here is my React Component:
import React, { Component } from "react";
import { fetchCoin } from "../redux/actions";
import { connect } from "react-redux";
class ViewCoin extends Component {
componentDidMount() {
this.props.fetchCoin(this.props.match.params.symbol);
}
render() {
console.log("Props:", this.props.coin);
return (
<div>
<h2>View Item Page</h2>
</div>
);
}
}
const mapStateToProps = state => {
return {
coin: state.coins.coin
};
};
export default connect(mapStateToProps, { fetchCoin })(ViewCoin);
This code returns the following object:
{
"BTC": {
"urls": {
"website": [
"https://bitcoin.org/"
],
"twitter": [],
"reddit": [
"https://reddit.com/r/bitcoin"
],
"message_board": [
"https://bitcointalk.org"
],
"announcement": [],
"chat": [],
"explorer": [
"https://blockchain.info/",
"https://live.blockcypher.com/btc/",
"https://blockchair.com/bitcoin/blocks"
],
"source_code": [
"https://github.com/bitcoin/"
]
},
"logo": "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png",
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"slug": "bitcoin",
"date_added": "2013-04-28T00:00:00.000Z",
"tags": [
"mineable"
],
"category": "coin"
}
}
When trying to access the coin name, for example, I issue the command:
console.log(this.props.coin.BTC.name);
I get the error: Cannot read property 'BTC' of undefined
I tested accessing values in this object outside of React and I am able to access the different values. How would one access a nested object like this using React.
Thanks in advance.

What's likely happening is the first time that component renders coin is undefined. When the action returns, the props are updated, the component re-renders and coin is logged out.
Do something like this in render and it should work:
this.props.coin && console.log('coin name:', this.props.coin.BTC.name)

Related

VueJS - Composition API - How to properly import and read a local JSON file

I am having some trouble loading in a local JSON file to read some data. It seems I can get the data to import as I can log it out to the console. If I do console console.log(rooms.value) for example I get the following in chrome browser:
I want to iterate through the data, however if I try a v-for="room in rooms" and output room.id for example I just get
Please can someone help?
JSON:
{
"rooms":[
{
"id": 1,
"name": "Master Bedroom",
"floor": 1,
"lights": true,
"blinds": true,
"climate": true,
"music": true
},
{
"id": 2,
"name": "Living Room",
"floor": 0,
"lights": true,
"blinds": true,
"climate": true,
"music": true
}
]
}
Vue:
<script>
import { ref } from 'vue'
import roomData from '../../data/db.json'
export default {
components: {},
setup(){
const rooms = ref(roomData)
console.log(rooms.value)
return { rooms }
}
}
</script>
you can call result of iteration with room.id not rooms.id, this might just be a typo in your template code.
I've worked it out - I could only access the array by assigning the constant to roomData.rooms (as opposed to just roomData). So working code looks like:
<script>
import { ref } from 'vue'
import roomData from '../../data/db.json'
export default {
components: {},
setup(){
const rooms = ref(roomData.rooms)
console.log(rooms.value)
return { rooms }
}
}
</script>
to import your json you can do this with SFC in vuejs3
<script setup>
import roomData from '../../data/db.json'
console.log(roomData)
</script>

When data changes that is passed to a Vue3 control, it doesn't update. It works when hard coded

This is the pared down code for the main component. I have reactive data elements setup here.
<template>
<div class="scheduler">
<recursive-render :items="data.probeGroupedData"/>
</div>
</template>
<script>
import { ref } from "vue";
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: "Scheduler",
components: {
RecursiveRender
},
setup() {
const data = ref({
probeGroupedData: [],
probeScriptInfo: [],
probeScriptInfoHashById: {},
});
return {
data,
};
},
methods: {
probeSort: function() {
var result = []
//I do various things to sort the data and fill up result and then:
this.data.probeGroupedData = result;
console.log("\nresult:" + JSON.stringify(result, null, ' '));
}
}
},
mounted() {
//I do various things to request the data here, and call this.probeSort
},
};
</script>
The component writes data just fine if I paste the data directly into it on setup.
probeGroupedData: [{
"id": "_1",
"label": "Renton, WA",
"keyName": "id",
"cssClass": "top",
"children": [
{
"label": "Motorola",
"id": "_0",
"cssClass": "manufacturer",
"children": [
{
"label": "Atrix",
"id": "_1",
"cssClass": "family",
"children": [
{
"label": "HD",
"id": "_2",
"cssClass": "model",
"children": [
{
"isLeaf": true,
"id": 1,
"cssClass": "device"
}
]
}
]
}
]
}]
But it isn't updating it when it is being written by probeSort. Do I need to have a watcher or something know that it has changed?
This is the entire code for RecursiveRender
<template>
<div>
<div v-for="item in data.items" :class="cssClass" :key="item.id">
<div v-if="item.isLeaf" :id="item.id">
{{item.label}}
</div>
<div v-if="item.children">
{{item.label}}
<recursive-render :items="item.children" />
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: 'RecursiveRender',
components: {
RecursiveRender
},
setup() {
const data = ref({
items: [],
});
return {
data
};
},
props: {
items: {
type: Array,
default: () => []
}
},
mounted() {
//this is where the data is received
this.data.items = this.items;
}
};
</script>
I think the issue may be due to use of ref instead of reactive
ref is for values like boolean, string, and number. It will not update on deep changes. If you have nested data like array or object, reactive might be a better option since it can look for deep data changes.
I also noticed you're not using data.value. this.data.probeGroupedData = result; is likely storing the data in the wrong place. You could try this.data.value.probeGroupedData = result; which may still have issues with reactivity. you could also do this.data.value = {...this.data.value, probeGroupedData : result} which will trigger the reactivity without worrying about the depth issue.
Thanks for the clues that Daniel gave me. The error is passing the value on mount. Just having the prop is enough, it doesn't have to be passed back to data.
<template>
<div>
<div v-for="item in items" :class="cssClass" :key="item.id">
<div v-if="item.isLeaf" :id="item.id">
{{item.label}}
</div>
<div v-if="item.children">
{{item.label}}
<recursive-render :items="item.children" />
</div>
</div>
</div>
</template>
<script>
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: 'RecursiveRender',
components: {
RecursiveRender
},
props: {
items: {
type: Array,
default: () => []
}
},
};
</script>
With more experience, I've learned you need to use provide / inject.
In the parent component, the data you want to have reactive needs to be provided:
//add provide to the import:
import { provide, ref } from "vue";
//in setup() you need to pass this to provide:
setup() {
const boardData = ref ({ <your data here> });
provide('boardData', boardData);
return {
boardData,
<and anything else>
}
}
In the child component you need to add inject to the import:
import { ref, inject } from "vue";
In setup you inject it using the same name as you used to provide it:
setup() {
const boardData = inject('boardData');
return {
boardData,
//and whatever else you need for the component
}
}
This is being used now for a backgammon game that has child pips, and it works well. For layout reasons the pips need to be in four groups of six, so I used a child component for them so I didn't have to have complex repeated loops in the main code. The pips now behave as they are all on rendered in the parent, and changes that the pips do to the reactive data are rendered properly in other sets of pips.

Get specific property name in an object

I am a newbie to react. I have an object. I am trying to get specific value in an object but I am unable to do so. In my case, I am finding the value "Miami, USA" in the description of startLocation. The data object is retrieved from the server through api request and redux action.
1)Uncaught Error: Objects are not valid as a React child (found: object with keys { description, address}). If you meant to render a collection of children, use an array instead.
2)Unhandled Rejection (TypeError): Cannot read property 'description' of undefined
The data I got back from server looks like this,
{
"startLocation": {
"description": "Miami, USA",
"address": "301 Biscayne Blvd, Miami, FL 33132, USA"
},
"name": "Running",
"description": "something", // not this one
}
import React, { Component } from 'react';
import './Page.css';
class Page extends Component {
// some code
render() {
const eventData = this.props.events.data;
const {
name,
startLocation,
description,
} = eventData;
console.log(startLocation["description"]) // undefined
return (
<div className="container">
<div className="heading-group">
<h1 className="header">
<span>{name}</span>
</h1>
<span className="header-text">
{startLocation["description"]}>
</span>
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
events: state.eventContainer,
});
export default connect(
mapStateToProps,
)(Page);
Any idea?
It works fine with the sample data provided. Perhaps your eventData hasn't imported properly.
const eventOne = {
"startLocation": {
"description": "Miami, USA",
"address": "301 Biscayne Blvd, Miami, FL 33132, USA"
},
"name": "Running",
"description": "something",
}
// destructure object javascript here
const {
name,
startLocation,
description,
} = eventOne;
console.log(description);
console.log(startLocation);
console.log(startLocation["description"]);

TypeError: Cannot read property 'map' of undefined when parsing JSON

I got the error: TypeError: Cannot read property 'map' of undefined when I am trying to build a const value to inject inside a Gallery. The const is build using a JSON
Here is the class where the issue happened:
class ClassDetails extends React.Component {
constructor(props, context) {
super(props);
this.state = {anchorEl: null,
showJoin: false,
classDetailsInfo: ''};
}
componentDidMount = () => {
this.setState({classDetailsInfo: ClassDataUseCase.getClassDetails()})
}
render() {
const CLASS_PIC_LIST = this.state.classDetailsInfo.classpic
const GALLERY = CLASS_PIC_LIST.map((pic) => ({
src: pic,
thumbnail: pic, //.replace("1280","_480"), // for example
thumbnailWidth: 156,
thumbnailHeight: 156
}));
...
}
}
export default ClassDetails;
The exact error is TypeError: Cannot read property 'map' of undefined and happened when doing const GALLERY = CLASS_PIC_LIST.map((pic) => ({
The classDetailsInfo is set using ClassDataUseCase.getClassDetails() defined as below:
class ClassDataUseCase {
static getAllClasses() {
return JSON.parse(ClassDetailsData.Classes)
}
static getClassDetails(id) {
var jsonClasses = JSON.parse(ClassDetailsData.Classes);
for(let k=0;k< jsonClasses.length;k++){
if(jsonClasses[k].id===id){
return jsonClasses[k];
}
}
}
}
and the data are coming from the ClassDetailsData which is a JSON as defined below:
class ClassDetailsData {
static Classes = [{
"id": "c000001",
"title": "Cook like a chef",
"price": "5",
"teacher": "Arthur Patrick",
"teacherpic": "https://cdn.pixabay.com/photo/2015/03/03/08/55/portrait-657116_1280.jpg",
"teacherid": "u0000000001",
"desc": "Always want to learn to cook, that's the place you need to be",
"bring": "Your fun , your motivation and you",
"tags": [],
"address": {
"id":"",
"Name": "Joel Robuchon",
"address1": "3799 S Las vegas boulevard",
"address2": "",
"city": "las vegas",
"county": "Clark",
"zip": "89109",
"state": "Nevada",
"country": "United States"
},
"date": "2021/09/01",
"time": "1:00PM",
"duration": "2",
"classpic": ["https://cdn.pixabay.com/photo/2014/06/16/23/10/spice-370114_1280.jpg",
"https://cdn.pixabay.com/photo/2015/08/13/18/47/spices-887348_1280.jpg",
"https://cdn.pixabay.com/photo/2015/04/20/13/30/kitchen-731351_1280.jpg",
"https://cdn.pixabay.com/photo/2015/07/02/10/40/writing-828911_1280.jpg"],
"reviews": [{
"name": "Gabby Caldwell",
"profilePic":"https://cdn.pixabay.com/photo/2015/03/03/08/55/portrait-657116_960_720.jpg",
"rate": "5",
"total_review": "13",
"text": "Rachel was such a kind and knowledgeable guide. She took us to see some hidden coves that a lot of tourists probabaly miss. I’m keeping the map I made FOREVER!!!"
}],
},
{........}
];
}
export default ClassDetailsData;
I do not understand why it's complaining. any idea ? thanks
There might be case when this.state.classDetailsInfo.classpic don't have the value. So initialize it with a default [] if it does not have any value.
You can do it like this:
const CLASS_PIC_LIST = this.state.classDetailsInfo.classpic || [];
In the constructor you are declaring your state with classDetailsInfo as a empty string, and a string doesn't have a .map method on it.
this.state = {anchorEl: null,
showJoin: false,
classDetailsInfo: ''};
You need to declare the classDetailsInfo as an appropriate type for it not to break on the initial render.
this.state = {anchorEl: null,
showJoin: false,
classDetailsInfo: {
classpic: []
}
};
In your state you define classDetailsInfo as a string, that's the reason why you are getting that error
You should set initially it equal to an empty array [] to avoid unexpected behavior
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
classList: ''
}
}
componentDidMount() {
this.setState({classList: ["name", "data", "test"]});
}
render() {
return <div>
{this.state.classList.map(cl => <span>{cl}</span>)}
</div>
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.1.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.1.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can look this simple example, If I change the value of state key classList to an [] every thing work as expected. But now It reproduces the same error you get
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
classList: []
}
}
componentDidMount() {
this.setState({classList: ["name", "data", "test"]});
}
render() {
return <div>
{this.state.classList.map(cl => <span>{cl}</span>)}
</div>
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In the second example after I initialize classList to an array it work.

How can I make an axios call before rendering and store a session?

I have tried many tutorials and so far, I can display items and get a little bit around React.
The URL structure is
/works/2
The query string of 2 is stored inside of pageID
I then fire the ajax call and filter the database to only show that data with .find()
This is the WorksPage.js file which will list the company work portfolio items.
import React, { Component } from 'react';
import axios from 'axios';
import './index.css';
class WorksPage extends Component {
constructor(props) {
super(props);
this.state = {itemList: []};
}
componentWillMount(){
const pageID = this.props.match.params.page;
axios.get('/api/works.json').then(function(response){
const result = response.data.find(i => i.id === pageID);
this.setState({ isLoaded: true, itemList: result });
}.bind(this));
}
componentDidMount() {
window.scrollTo(0, 0);
}
render(){
return(
<div className="workListing pageWrapper">
<section className="workListing-process">
<div className="smcontainer center txtc">
{this.state.itemList}
App Type: {this.state.itemList.sub}
App cars: {this.state.itemList.cars.map((car, i) =>
<div key={i}>
{car}
</div>
)}
</div>
</section>
</div>
)
}
}
export default WorksPage;
My JSON of works.json is
[{
"id": 0,
"img": "/images/slider.jpg",
"header": "GPS Module",
"sub": "iOS",
"link": "/works/0",
"cars":[ "Ford", "BMW", "Fiat" ]
}{
"id": 1,
"img": "/images/map-slider.jpg",
"header": "GPS Mapping Vectors",
"sub": "iOS",
"link": "/works/1",
"cars":[ ]
},{
"id": 2,
"img": "/images/slider.jpg",
"header": "GPS Module",
"sub": "Android",
"link": "/works/2",
"cars":[ "Ferrari", "BMW", "Land Rover" ]
}]
So far the {this.state.itemList} returns blank. The car list loop is also not working. Console.log will return the data of result if I do the console.log after the this.setState
First of all, do not use componentWillMount, it's both deprecated and not meant for calling APIs. Use componentDidMount instead.
I assume the problem is that pageID is string and id is number, thus nothing gets matched. Try converting pageID to number before comparing it.
const pageID = parseInt(this.props.match.params.page, 10);
You use 'this' keyword in callback function, and it refers to the call back itself not the component.
and also use componentDidmount, componenwillmount is not used anymore.
see here : componentWillMount
use this :
componentDidMount(){
let that = this;
const pageID = this.props.match.params.page;
axios.get('/api/works.json').then(function(response){
const result = response.data.find(i => i.id === pageID);
that.setState({ isLoaded: true, itemList: result });
};
}

Categories