Rendering a nested object state - reactjs

This problem is giving me a huge headache so any help is welcome :)
In this component, I'm making 2 axios calls to different APIs: one for freegeoip and one for openweathermap. I'm storing the data in the currentCity state, which is an object with 2 keys, location and weather. The idea is that the app detects your current location (using freegeoip) and renders location name and weather data (using openweathermap).
I'm positive it's storing the state properly as console logs have confirmed. I can render the location data for currentCity state, but can't seem to render the weather data for currentCity state.
renderCurrentCity(city) {
console.log('state3:', this.state.currentCity);
console.log([city.weather.main]);
return(
<div>
<li>{city.location.city}, {city.location.country_name}</li> // Working
<li>{city.weather.main.temp}</li> // Not working
</div>
)
}
The console error I get:
Uncaught (in promise) TypeError: Cannot read property '_currentElement' of null
currentCity.location JSON:
{
"ip": // hidden,
"country_code": "FR",
"country_name": "France",
"region_code": "GES",
"region_name": "Grand-Est",
"city": "Reims",
"zip_code": "",
"time_zone": "Europe/Paris",
"latitude": 49.25,
"longitude": 4.0333,
"metro_code": 0
}
currentCity.weather JSON:
{
"coord": {
"lon": 4.03,
"lat": 49.25
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"base": "stations",
"main": {
"temp": 283.15,
"pressure": 1011,
"humidity": 43,
"temp_min": 283.15,
"temp_max": 283.15
},
"visibility": 10000,
"wind": {
"speed": 3.1,
"deg": 350
},
"clouds": {
"all": 0
},
"dt": 1493127000,
"sys": {
"type": 1,
"id": 5604,
"message": 0.1534,
"country": "FR",
"sunrise": 1493094714,
"sunset": 1493146351
},
"id": 2984114,
"name": "Reims",
"cod": 200
}
Rest of code:
import React, { Component } from 'react';
import axios from 'axios';
import WeatherList from './weatherlist';
import SearchBar from './searchbar';
const API_KEY = '95108d63b7f0cf597d80c6d17c8010e0';
const ROOT_URL = 'http://api.openweathermap.org/data/2.5/weather?'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
cities: [],
errors: '',
currentCity: {
location: {},
weather: {}
}
};
this.currentCity();
this.renderCurrentCity = this.renderCurrentCity.bind(this);
}
citySearch(city) {
const url = `${ROOT_URL}&appid=${API_KEY}&q=${city}`;
axios.get(url)
.then(response => {
const citiesArr = this.state.cities.slice();
this.setState({
cities: [response.data, ...citiesArr],
errors: null
});
})
.catch(error => {
this.setState({
errors: 'City not found'
})
})
}
currentCity() {
var city;
var country;
axios.get('http://freegeoip.net/json/')
.then(response => {
const lat = response.data.latitude;
const lon = response.data.longitude;
city = response.data.city;
country = response.data.country_name;
const url = `${ROOT_URL}&appid=${API_KEY}&lat=${lat}&lon=${lon}`;
const state = this.state.currentCity;
console.log('state1:',state);
this.setState({
currentCity: { ...state, location: response.data }
});
console.log(url);
axios.get(url)
.then(city => {
const state = this.state.currentCity;
console.log('state2:', state);
this.setState({
currentCity: { ...state, weather: city.data }
});
})
})
}
renderCurrentCity(city) {
console.log('state3:', this.state.currentCity);
console.log([city.weather.main]);
return(
<div>
<li>{city.location.city}, {city.location.country_name}</li>
<li>{city.weather.main.temp}</li>
</div>
)
}
render() {
return (
<div className={this.state.cities == false ? 'search': 'search-up'}>
<h1>What's the weather today?</h1>
<ul className='list-unstyled text-center'>
{this.renderCurrentCity(this.state.currentCity)}
</ul>
<SearchBar
onSearchSubmit={this.citySearch.bind(this)}
errors={this.state.errors} />
{this.state.cities == false ? null : <WeatherList cities={this.state.cities} />}
</div>
)
}
}

You are spreading your whole state when your receive the weather data:
this.setState({
currentCity: { ...state, weather: city.data }
});
it should be:
this.setState({
currentCity: { ...state.currentCity, weather: city.data }
});

Related

How to get separate daily array from the weather API array (For React Redux) which gives a 5 days every 3 hours report

The Openweather API provides 5 days forecast for every 3 hours. We need to display data only for the current day. How can I separate the data for the current day and bind data only for the current day and each hour in that day? Here is the code:
Data looks like this:
{
"data": {
"cod": "200",
"message": 0.0062,
"cnt": 39,
"list": [
{
"dt": 1540177200,
"main": {
"temp": 24.55,
"temp_min": 20.88,
"temp_max": 24.55,
"pressure": 1008.67,
"sea_level": 1025.96,
"grnd_level": 1008.67,
"humidity": 58,
"temp_kf": 3.67
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "02d"
}
],
"clouds": {
"all": 8
},
"wind": {
"speed": 3.82,
"deg": 340.5
},
"sys": {
"pod": "d"
},
"dt_txt": "2018-10-22 03:00:00"
},
{
"dt": 1540188000,
"main": {
...
},
},
{
"dt": 1540198800,
"main": {
...
},
},
{
"dt": 1540587600,
"main": {
. . .
}
}
]
}
Redux Saga
export const fetchWeatherListStart = () => ({
type: ActionTypes.FETCH_WEATHER_START
});
...
import { takeLatest, put } from "redux-saga/effects";
function* fetchWeatherSaga(){
try {
const weatherResponse = yield fetch("https://samples.openweathermap.org/data/2.5/forecast?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02")
const weatherlist = yield weatherResponse.json()
yield put(fetchWeatherSuccess(weatherlist));
} catch (error) {
yield put(fetchWeatherError(error.message));
}
}
export default function* watchFetchWeatherSaga(){
yield takeLatest("FETCH_WEATHER_START", fetchWeatherSaga)
}
Reducer
const INITIAL_STATE = {
weatherList: null,
isFetching: false,
errorMessage: undefined
};
const fetchWeatherListReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case "FETCH_WEATHER_START":
return {
...state,
isFetching: true
};
case "FETCH_WEATHER_SUCCESS":
return {
...state,
isFetching: false,
weatherlist: action.payload
};
case "FETCH_WEATHER_ERROR":
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default fetchWeatherReducer;
Component
export const WeatherPage = ({ fetchWeatherListStart}) => {
useEffect(() => {
fetchWeatherListStart();
}, [fetchWeatherListStart]);
...
I thought about using a selector but not sure if it's the best approach... How can be it solved?
You can do it on reducer level, the only thing you need is the current date stamp
case "FETCH_WEATHER_SUCCESS":
// pass today's date along when calling this action
const myDate = action.payload.todayDate
// get only data for today
const filteredList = action.payload.list.filter(item => item.dt === myDate)
return {
...state,
isFetching: false,
weatherlist: filteredList
};

How to get array format value in json using reactjs?

I added my coding's for how to create state like at the end of this question above coding's are i will try but am not able to create like that state.
This is my json format coding to get through
"Results": {
"Piechart":[{
"labels": ["Under 18", "Age 18-54", "Age 55+"],
"datasets": [{
"data": [2000, 4000, 2850],
"backgroundColor": ["red", "blue", "green"]
}]
}]
}
Here is my React Code
this.state = {labels: [], datasets: []};
componentDidMount() {
this.pieChart().then(result => {
this.setState({diagramData: result.pieDiagramLabel});
}).catch(err => console.error(err));
}
async pieChart() {
let pieChart = await import('../data/result_log.json');
console.log(pieChart[0].Results.Piechart);
return {
pieDiagramLabel: pieChart[0].Results.Piechart.labels
}
}
How to get both label and datasets? And how to set into state like below format
this.state= {
"labels": ["Under 18", "Age 18-54", "Age 55+"],
"datasets": [{
"data": [2000, 4000, 2850],
}]
}
You can try like this
data.json
{
"Piechart": [
{
"labels": ["Under 18", "Age 18-54", "Age 55+"],
"datasets": [
{
"data": [2000, 4000, 2850],
"backgroundColor": ["red", "blue", "green"]
}
]
}
]
}
App.js
import React from "react";
import Results from "./data.json";
export default class App extends React.Component {
state = {
labels: [],
datasets: []
};
componentDidMount() {
this.setState({
labels: Results.Piechart[0].labels,
datasets: Results.Piechart[0].datasets
});
}
render() {
const { labels, datasets } = this.state;
return (
<div className="App">
{labels.map((label, index) => (
<p key={index}>{label}</p>
))}
<hr />
{datasets.map((data, index) => (
<p key={index}>{data.backgroundColor}</p>
))}
</div>
);
}
}
Live working demo https://codesandbox.io/s/fetch-local-json-6bq4m

How do I loop an array in React Native

The RSS feed is parsed into an array, but the promblem is, the array doesn't loop, it only shows 1 item. How do I loop my Feeds array?
This is my code, I use react-native-rss-parser (https://www.npmjs.com/package/react-native-rss-parser):
class TNScreens extends React.Component {
state = {
feed: [],
title: []
};
componentDidMount() {
return fetch("https://vnexpress.net/rss/tin-moi-nhat.rss")
.then(response => response.text())
.then(responseData => rssParser.parse(responseData))
.then(rss => {
for (let i = 0; i < rss.items.length; i++) {
this.setState(prevState => ({
...prevState,
feed: rss.items[i],
title: rss.items[i].title
}));
}
});
}
render() {
const Feeds = ([
{
pic: {uri: ''},
title: Object.keys(this.state.title).map(i => this.state.title[i])
}
]);
return (
<SafeAreaView>
<ScrollView>
<Text h2 h2Style={styles.h2Style}>
Trang nhất
</Text>
<Text h4 h4Style={styles.h4Style}>
Cập nhật siêu nhanh
</Text>
<View>
{Feeds.map(({ pic, title }) => (
<Tile
imageSrc={pic}
height={200}
activeOpacity={0.9}
title={title}
titleNumberOfLines={1}
titleStyle={styles.title}
featured
key={title}
/>
))}
</View>
</ScrollView>
</SafeAreaView>
);
}
}
export default TNScreen;
UPDATE
console.log(rss) result:
Object {
"authors": Array [],
"categories": Array [],
"copyright": undefined,
"description": "VnExpress RSS",
"image": Object {
"description": undefined,
"height": undefined,
"title": "Tin nhanh VnExpress - Đọc báo, tin tức online 24h",
"url": "https://s.vnecdn.net/vnexpress/i/v20/logos/vne_logo_rss.png",
"width": undefined,
},
"items": Array [],
"itunes": Object {
"authors": Array [],
"block": undefined,
"categories": Array [],
"complete": undefined,
"explicit": undefined,
"image": undefined,
"newFeedUrl": undefined,
"owner": Object {
"email": undefined,
"name": undefined,
},
"subtitle": undefined,
"summary": undefined,
},
"language": undefined,
"lastPublished": "Sat, 30 Nov 2019 21:28:12 +0700",
"lastUpdated": undefined,
"links": Array [
Object {
"rel": "",
"url": "https://vnexpress.net/rss/tin-moi-nhat.rss",
},
],
"title": "Tin mới nhất - VnExpress RSS",
"type": "rss-v2",
}
There is a problem in your code in for loop you are just replacing the state with one value you are not copying the previous state. You will have to
this.setState(prevState => ({
...prevState,
feed: [...prevState.feed, rss.items[i]],
title: [...prevState, rss.items[i].title]
}));
But this is not the best practice because on every iteration your render will re-render so the best practice is
const feeds = rss.items.map(item => item);
const titles = rss.items.map(item => item.title);
this.setState({ feed: feeds, title:titles });
your this.state.x only save one item, change the code to the following:
and you should also change the get Data method, please do not setState in a loop
componentDidMount() {
return fetch("https://vnexpress.net/rss/tin-moi-nhat.rss")
.then(response => response.text())
.then(responseData => rssParser.parse(responseData))
.then(rss => {
let copyRess = []
let copyTitle = []
for (let i = 0; i < rss.items.length; i++) {
copyRess.push(rss.items[i]);
copyTitle.push(rss.items[i].title)
}
this.setState(prevState => ({
...prevState,
feed: copuRess,
title: copyTitle
}));
});
}
render() {
const Feeds = []
this.state.title.map(element => {
Feeds.push({pic:"",title:element})
})
....

Render JSON data from API call in react

Working on an app using the google maps API for geolocation with Reactjs. My aim right now is to simply render the entire JSON data to the window (will be used later). No errors, but nothing is rendering to the page. Am I trying to access the data inccorrectly?
class App extends Component {
constructor () {
super();
this.state = {
isLoaded: false,
results: {}
};
}
componentDidMount() {
fetch(geo_url)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
results: result.results
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const {error, isLoaded, results} = this.state;
if (error) {
return <div>Error: {error.message} </div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className="App">
Location Data:
{results.geometry}
</div>
);
}
}
}
Below is a sample of the JSON i'm trying to access:
{
"results": [
{
"address_components": [
{
"long_name": "1600",
"short_name": "1600",
"types": [
"street_number"
]
},
{
"long_name": "Amphitheatre Parkway",
"short_name": "Amphitheatre Pkwy",
"types": [
"route"
]
},
{
"long_name": "Mountain View",
"short_name": "Mountain View",
"types": [
"locality",
"political"
]
},
{
"long_name": "Santa Clara County",
"short_name": "Santa Clara County",
"types": [
"administrative_area_level_2",
"political"
]
},
{
"long_name": "California",
"short_name": "CA",
"types": [
"administrative_area_level_1",
"political"
]
},
{
"long_name": "United States",
"short_name": "US",
"types": [
"country",
"political"
]
},
{
"long_name": "94043",
"short_name": "94043",
"types": [
"postal_code"
]
}
],
"formatted_address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
"geometry": {
"location": {
"lat": 37.422617,
"lng": -122.0853839
},
"location_type": "ROOFTOP",
"viewport": {
"northeast": {
"lat": 37.4239659802915,
"lng": -122.0840349197085
},
"southwest": {
"lat": 37.4212680197085,
"lng": -122.0867328802915
}
}
},
"place_id": "ChIJ2eUgeAK6j4ARbn5u_wAGqWA",
"plus_code": {
"compound_code": "CWF7+2R Mountain View, California, United States",
"global_code": "849VCWF7+2R"
},
"types": [
"street_address"
]
}
],
"status": "OK"
}
First render occurs before results are retrieved. Check in render() whether results exist already. If not, display a 'loading' message.
In addition to that, fix your handling of error while trying to retrieve data. You are setting a state.error variable which was not defined. Then, in render, display an error message if loading is done but there is an error.
First, you have to do :
<div className="App">
{this.state.results.geometry}
</div>
or
render() {
const {results} = this.state
return (
<div className="App">
{results.geometry}
</div>
);
}
}
But Like #Yossi saids, you result are not defined in you first render. That's why you have : "results not defined". You can use "lodash" to force your render. It's works even if I don't know if it's a good practice
You can test :
import _ from 'lodash';
render() {
const {results} = this.state
return (
{!_.isEmpty(results) &&
<div className="App">
{results.geometry}
</div>
}
);
}
It should be works :)
PS : sorry for my bad english ;)
You can set in state a key for error: false.
In componentDidMount its better to use then and catch for errors
componentDidMount() {
fetch(geo_url)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
results: result.results
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
).catch(error) {
this.setState({
isLoaded: true,
error
})
}
}

How to use FetchAPI nested Arrays of same object

I am trying to show menu where it can have parent child relationship, with flat structure it is working fine but with nested objects not able to do it.
The below is the JSON and reactJS code.
JSON -
{
"data": [
{
"to": "#a-link",
"icon": "spinner",
"label": "User Maintenance"
},
{
"content": [
{
"to": "#b1-link",
"icon": "apple",
"label": "System Controls"
},
{
"to": "#b2-link",
"icon": "user",
"label": "User Maintenance"
}
],
"icon": "gear",
"label": "System Preferences"
},
{
"to": "#c-link",
"icon": "gear",
"label": "Configuration"
}
]
}
ReactJS code -
export default class MenuComponent extends Component {
constructor() {
super();
this.state = {}
}
componentDidMount(){
fetch('http://localhost:8084/Accounting/rest/v1/company/menu?request=abc')
.then(response => response.json())
.then(parsedJSON => parsedJSON.data.map(menu => (
{
to: `${menu.to}`,
icon: `${menu.icon}`,
label: `${menu.label}`
// content: `${menu.content}`
}
)))
.then(content => this.setState({
content
}))
}
render() {
console.log('333');
console.log(this.state.content);
return (
<MetisMenu content={this.state.content} activeLinkFromLocation />
)
}
}
In JSON you can see the 'System Preference' has nested content.
Try this code
class MenuComponent extends Component {
constructor() {
super();
this.state = {
content : []
}
}
componentDidMount(){
fetch('http://localhost:8084/Accounting/rest/v1/company/menu?request=abc')
.then(response => response.json())
.then(parsedJSON => parsedJSON.data.map(menu => (
{
to: `${menu.to}`,
icon: `${menu.icon}`,
label: `${menu.label}`
// content: `${menu.content}`
}
)))
.then(content => this.setState({
content
}))
}
render() {
const {content} = this.state
if(content === undefined){
return <div>Content not found</div>;
}
if(content.length === 0){
return <div>Loading</div>;
}
return (
<MetisMenu content={content} activeLinkFromLocation />
)
}
}
export default MenuComponent

Resources