rendering a nested objected in reactjs - javascript

I am having a hard time to render a nested object on to a reactjs page
import React, { Component } from "react";
import Toolpanel from "./Todopanel";
import Toollist from "./Toollist";
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
city: "Auckland",
cityWeather: {}
};
this.updateUser = this.updateUser.bind(this);
}
updateUser(entry) {
console.log(entry);
let item = {
text: entry,
key: Date.now()
};
this.setState(prevstate => {
return {
users: prevstate.users.concat(item)
};
});
}
componentDidMount() {
let apiId = "***************************";
let city = this.state.city;
let ApiString =
"http://api.openweathermap.org/data/2.5/weather?q=" +
city +
"&APPID=" +
apiId;
fetch(ApiString)
.then(results => results.json())
.then(data => this.setState({ cityWeather: data }));
}
render() {
let test = this.state.cityWeather;
return (
<div>
<Toolpanel parentUpdate={this.updateUser} />
<div>wind speed : {test.wind.speed} </div>
</div>
);
}
}
export default App;
I have added my JSON file that I received from my weather API
//Json file
{
"coord": { "lon": 174.77, "lat": -36.85 },
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04n"
}
],
"base": "stations",
"main": {
"temp": 293.7,
"pressure": 1018,
"humidity": 77,
"temp_min": 293.15,
"temp_max": 294.26
},
"visibility": 10000,
"wind": { "speed": 5.1, "deg": 360 },
"clouds": { "all": 92 },
"dt": 1553672420,
"sys": {
"type": 1,
"id": 7345,
"message": 0.0043,
"country": "NZ",
"sunrise": 1553624951,
"sunset": 1553667823
},
"id": 2193733,
"name": "Auckland",
"cod": 200
}
I am trying to render the wind speed from the JSON to my page.. but is throwing me a error message saying "TypeError: Cannot read property 'speed' of undefined"...Please help. I am fairly new to ReactJs.

If you look at the code, here is the sequence of events:
Component is created, i.e. constructor is called
Component is mounted, i.e. componentDidMount is called
componentDidMount starts an async request to fetch the data which is then parsed and set in state.
render method tries to read the data from state.
Now, since the request in #3 is an async one, it may not have completed in time when the render method has been called the first time.
So, you need to check if your request has completed or failed or is running.
You can use that to conditionally render the content in your render method.
Recommended reading
The official reactjs blog entry on async rendering with examples of when data is fetched from an external resource

You're not wrong the way you approached it. The error you're getting is because the fetch you're performing is taking some time, and render first executes without having the data populated.
So first time it gets in your render method the value of test = {}. So test.wind.speed will throw an error.
Instead, show a loading state of some sort or simply return null until the call is performed:
render() {
let test = this.state.cityWeather;
if (!test) {
return 'Loading...';
}
....
}

You are accessing the properties too fast since fetch is an asynchronous call it will take some time but your render fires before that already.
Use it like this
{ test && <div>wind speed : {test.wind.speed} </div>}

Initially your test will be null as you haven't received any response from your API so you should check the variable presence before using it. So just check if it is present before using it like this:
render() {
let test = this.state.cityWeather;
return (
<div>
<Toolpanel parentUpdate={this.updateUser} />
<div>wind speed : {test && test.wind && test.wind.speed ? test.wind.speed : ''} </div>
</div>
);
}

Since you didn't post ToolPanel Component implementation, I may be wrong (I'm missing some information). But, I'm also pretty sure that your problem is not having a loading variable.
Basically, the first time render() method is called, you have this.state.cityWeather to be an empty object {}; that is because you fetch the data in componentDidMount(). Thus, the first time render() is called, being this.state.cityWeather empty, you cannot access this.state.cityWeather.wind.speed because you don't have the property wind in this.state.cityWeather!
So, usually, the common way to do this is adding a property loading in the state, and setting it to true in the constructor. Then, in the callback of the fetch, while you set the data in this.state.cityWeather, you also set loading to true.
Finally, in the render() method you wrote a conditional rendering: if this.state.loading === true, then you print a simple paragraph like <p>I'm retrieving info!</p>, otherwhise, if this.state.loading === false, you can render what you want.

Related

Why I cannot render AJAX JSON value?

Here is my code:
<ul>
<li v-for="value in RandomTopic" :key="value.id">{{ value.title }}</li>
</ul>
export default {
data() {
return {
RandomTopic: null
}
},
mounted() {
///some method to get data from remote server
console.log(res.data);
this.RandomTopic = res.data;
}
}
I want to render all the data from the remote server in front end. However, after the program ran, it reports this error:
Cannot set property 'RandomTopic' of undefined ; at api request success callback function
TypeError: Cannot set property 'RandomTopic' of undefined
The console.log(res.data); log the JSON successfully so it seems not the problem of AJAX or remote server.
And also, here is a sample of the JSON:
[
{
"id": 421,
"title": "sample1",
"image_url": "bus.png"
},
{
"id": 535,
"title": "sample78",
"image_url": "car.png"
}
]
What's wrong with my code ? I am a beginner of Vue 3, please help me.
As per the error you mentioned, Issue is basically related to the scope of this. Looks like you are using regular function instead of arrow function ( => {...}) which don't provide their own this binding (it retains this value of the enclosing lexical context).
Reference - Arrow function
.then(res => {
this.RandomTopic = res.data;
})

React Get data from json-server and display in slider

i have a JSON as a server, the JSON have a 'recipes' array which have objects into it
"recipes": [
{
"id": "1",
"title": "Arepa",
"description": "...",
"image": "...",
"preparation": "...",
"ingredients": "..",
"notes": "..."
},
{
"id": "2",
"title": "Burritos",
"description": "...",
"image": "...",
"preparation": "...",
"ingredients": "...",
"notes": "..."
}
Iḿ trying to display the images into a slider with a loop,but the only thing im getting is errors and frustration,i cant get data easily but display it get me errors, I've tried so many thing and nothing work. this is my code.
TopTen.js
import { CarouselProvider, Slider, ButtonBack, ButtonNext } from 'pure-react-carousel';
const Topten = () => {
return (
<div id="topTenContainer">
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
>
<Slider>
//// here must be the images ////
</Slider>
<ButtonBack>Back</ButtonBack>
<ButtonNext>Next</ButtonNext>
</CarouselProvider>
</div>
);
};
export default Topten;
when i tried create a async function and tried put into i have this error => Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.here the example
import database from "../api/database"
const Sliderr = async() =>{
const responde = await database.get('recipes');
const recipes = responde.data;
return(
{
recipes.map(post =>{
return(
<div key={post.id}>
<h1>{post.title}</h1>
</div>
)
})
}
)
}
export default Sliderr
any help please?
Can you print "recipe" and "responde" values ​​in the console? if you can print it, try pulling the constants of the specified variables from "const" to "var".First of all, make sure of the returned answer. Perform step by step operations.
recipes?.map(post =>{
return(
<div key={post?.id}>
<h1>{post?.title}</h1>
</div>
)
})
you have to check if data has title field render the h1 component with using mark question
Well, the main problem is in fact as stated in the log:
Your Sliderr Component should return a NON async function. The function/functional-component "Sliderr" returns a Promise when you use it inside the jsx block in TopTen.js as it is declared as an async function.
You could handle the async aspect inside a useEffect e. g.
const Topten = () => {
const [data, setData] = useState([]);
useEffect(() => {
const getPosts = async () => setData(await database.get('recipes'));
getPosts();
}, []);
return (
<div id="topTenContainer">
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={data.length}
>
<Slider>
{data.map(post => <Slide>{post.title}</Slide>)}
</Slider>
<ButtonBack>Back</ButtonBack>
<ButtonNext>Next</ButtonNext>
</CarouselProvider>
</div>
);
};
You still would need to refactor your Sliderr Component into Slide though taking post.title as the children-prop.
Building on that you could think about extracting the api call into a custom hook.
[Edit: Sorry about my optimistic approach. I left out the error handling for the request]

Navigating to dynamically routed page in Next JS returns error

When navigating from a link in the same web app to the dynamically routed page clicking on a link the result is as intended: I navigate to the page for a product (http://localhost/1).
But when I directly navigate by naming the product number specifically in the search bar (navigating to http://localhost/2), I get the following error:
Server Error
TypeError: Cannot read property 'image' of undefined
> | <Image src={"/../public/images/" + p.image}
^
So far I've tried making the types match and reading the Next JS docs on dynamically routing.
I've removed the array zero from the filter but still no resolution.
Could it be possible that the routing only works when clicking on a link in Next JS? Is there some missing setting I've neglected?
pages/[pid].js
import { useRouter } from 'next/router'
import Image from 'next/image'
import data from '../products.json'
export default function Template() {
const router = useRouter()
const { pid } = router.query
const p = data.filter(product => product._id == pid)[0] // Choose one result
return (
<Image src={"/../public/images/" + p.image}
height="500px"
width="500px" />
)
}
products.json
[
{
"_id": 1,
"name": "Toyota",
"image": "toyota.png"
},
{
"_id": 2,
"name": "BMW",
"image": "bmw.png"
}
]
Update: I've tried to hardcode the src attribute in the Image tag and the new error says the other references are the issue. So I can safely say the issue is to do with no object returned when the data object is called.
I solved the issue!
It was not enough to use Dynamic Routes by using the 'useRouter()' function. I also had to define these two functions:
export async function getStaticProps(context) {
// No-op since getStaticPaths needs getStaticProps to be called.
return { props: {} }
}
export async function getStaticPaths() {
const dynamicFiles = products.map(product => (
{
params: { pid: String(product._id) }, // Product IDs are ints in JSON file
}
))
return {
paths: dynamicFiles,
fallback: false
}
}
This makes sense since you wouldn't want random paths to be used as a variable. For example, then a user would be able to specify http://localhost/1234 when 1234 is not a valid option.
https://nextjs.org/learn/basics/dynamic-routes/implement-getstaticprops

Audio not pausing in ReactJS

componentWillMount() {
console.log('Component WILL MOUNT!')
axios.get('/channels').then( (res) => {
//console.log(res.data.data.playList);
let playlists = [];
res.data.data.playList.map((value, key) => playlists.push(new Audio(value.url)));
this.setState((prevState) => {
return { audioList: playlists, categories: res.data.data.playList }
}, () => console.log(this.state.audioList));
}).catch( (err) => {
console.log(err);
});
}
**I also call this in componentDidUpdate() **
The above code that I used in my ReactJS web app to retrieve data from my DB that looks something like:
{
"_id": {
"$oid": "5a2b903abcf92a362080db4f"
},
"name": "test",
"playList": [
{
"url": "https://p.scdn.co/mp3-preview/a3fd5f178b7eb68b9dba4da9711f05a714efc966?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Lil Pump",
"songName": "D Rose",
"_id": {
"$oid": "5a2c5631e54ca10eb84a0053"
}
},
{
"url": "https://p.scdn.co/mp3-preview/155643656a12e570e4dda20a9a24d9da765b9ac5?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Tee Grizzley",
"songName": "From The D To The A (feat. Lil Yachty)",
"_id": {
"$oid": "5a2c5631e54ca10eb84a0054"
}
}
],
"__v": 0
}
I retrieve the url for each songs and store it inside my state this.state.audioList to make a playable list.
I access each song with an index
So, this.state.audioList[0] would be the first song.
When I try to play this music by doing
this.state.audioList[0].play(), this totally works fine.
The problem is when I try to pause it.
this.state.audioList[0].pause() does not pause the song for some reason.
I am assuming that it is because the this.state.audioList is getting updated every time and the Audio object that I am trying to pause is a new object that has nothing to do with the one currently being played.
Am I right? If so, is there a solution to this issue?
Please help!
That should work in componentWillMount although componentDidMount is preferred, see : https://reactjs.org/docs/react-component.html#componentdidmount
Quoted from this link (remote endpoint being your axios URL here) :
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
But you're almost certain that won't work if you put your axios.get() request in componentDidUpdate, because this method is called each time your component has been updated and re-rendered.
From the React Component Lifecycle document, you'll see that componentWillMount and componentDidMount both stay in the Mounting section (that is, they are called only once when the components DOM element are inserted in the DOM), whereas componentDidUpdate is in the Updating section, and therefore is called each time your component's state or props are changed (what happens when your axios.get() promise is fulfilled).
Also, as map returns an array, why not assign its returned value to your audioList ? Here is an example that you may want to try (untested though, sorry !):
componentDidMount() {
console.log('Component DID MOUNT!')
axios.get('/channels').then( (res) => {
//console.log(res.data.data.playList);
this.setState({
audioList: res.data.data.playList.map(value => new Audio(value.url)),
categories: res.data.data.playList
});
}).catch( (err) => {
console.log(err);
});
}
Hope this helps!

Using React to call Nested API?

Say I had the following JSON file:
{
"farmer": [
{
"crops": "corn"
}
],
"activities":{
"hobbies": "swimming"
},
"name: Todd"
}
I would like to know how to make calls to them using React. My best attempt is as shown below.
componentDidMount: function(){
var selfish = this;
$.get('~~someurl~~', function(data){
selfish.setState(data);
});
},
render: function(){
return (<div>
<p>{this.state.name}</p>
<p>{this.state.activities.hobbies}</p>
<p>{this.state.farmer[0].crops}</p>
</div>)
}
I get that the first one {this.state.name} will run as it has been written. However, I am uncertain as to how to express the final two {this.state.activities.hobbies} and {this.state.farmer[0].crops} using the React syntax.
I have written them out in hopefully a way that expresses what I am trying to achieve with each call.
EDIT: The specific error that results from the latter two state that they are undefined.
EDIT: So I got the second one working by adding an empty initial state.
getInitialState: function(){
return {activities: '', farmer: ''}
}
However, this leaves the last one, which still returns an error.
The problem is that you are using componentDidMount when you should use componentWillMount. Check out the documentation on these lifecycle methods.
This is what the documentation says about componentDidMount
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs.
Which means when you first render your states are not declared unless you have used getInitialState before (another lifecycle method).
I simply did this:
componentWillMount: function () {
this.setState({
"farmer": [
{
"crops": "corn"
}
],
"activities":{
"hobbies": "swimming"
},
"name": "Todd"
});
},
and I was able to use this.state.farmer[0].crops in my render method
EDIT:
To be clear. If you need to retrieve the data after you rendered the component then you need to specify default values for the states that you use in render. This can be achieved using getInitialState:
getInitialState: function () {
return {
"farmer": [
{
"crops": DEFAULT_VALUE
}
],
"activities":{
"hobbies": DEFAULT_VALUE
},
"name": DEFAULT_VALUE
});
},

Categories