I am trying to build a react app for weather. I am using openweathermap api.
i have a component that supposed to render 5 weather components, each one for each day (it's a 5 day forecast).
however, something is not working.
this is the weather component:
class Weather extends Component{
getAllDates = () =>{
const list = this.props.list;
let date = list[0]["dt_txt"].split(" ")[0];
const datesList = [{date: date, indexes: [0]}];
let indexes = [];
for(let i = 1; i < list.length; i++){
const currDate = list[i]["dt_txt"].split(" ")[0];
if(date !== currDate){
datesList.push({
date: currDate,
indexes: indexes});
date = currDate;
indexes = [];
}
else{
const toUpdate = datesList.pop();
const oldIndexes = toUpdate.indexes;
const newIndexes = oldIndexes.push(i);
toUpdate.indexes = newIndexes;
datesList.push(toUpdate);
}
}
return datesList;
}
render(){
const datesList = this.getAllDates();
return(
<React.Fragment>
{datesList.map((date, key) => {
return <DayWeather date = {date}
key = {key}
forecastList = {this.props.list} />
})}
</React.Fragment>
)
}
and in App.js I am rendering it conditionally like this:
{this.state.data === undefined ? <h1>Choose City</h1> :
<Weather list = {this.state.data.list}/>}
The problem is, the getAllDates is called twice from the render function in Weather component.
the loop goes for 2 iterations instead of 40, and then getAllDates is called again. I can't understand why because I have conditional rendering in App.js.
Also, the indexes variable at some point turns into a number and not an array,
and then I get an error that I can't do .push to indexes.
I have no idea what is hapenning here,
would appreciate any kind of help!
thank you!
**here I am fetching the data:
class App extends Component{
constructor(){
super();
this.state = {
data: undefined
}
}
onSelectHandler = (event) =>{
const city = event.target.value;
const apiCall = `http://api.openweathermap.org/data/2.5/forecast?q=${city},il&APPID=${API_KEY}`
fetch(apiCall)
.then(response => response.json())
.then(response =>
{
this.setState({
data: response
})
}
)
}
Related
I'm currently fetching data in Component1, then dispatching an action to update the store with the response. The data can be seen in Component2 in this.props, but how can I render it when the response is returned? I need a way to reload the component when the data comes back.
Initially I had a series of functions run in componentDidMount but those are all executed before the data is returned to the Redux store from Component1. Is there some sort of async/await style between components?
class Component1 extends React.Component {
componentDidMount() {
this.retrieveData()
}
retrieveData = async () => {
let res = await axios.get('url')
updateParam(res.data) // Redux action creator
}
}
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
sortData = props => {
const { param } = this.props
let result = param.sort((a,b) => a - b)
}
}
mapStateToProps = state => {
return { param: state.param }
}
connect(mapStateToProps)(Component2)
In Component2, this.props is undefined initially because the data has not yet returned. By the time it is returned, the component will not rerender despite this.props being populated with data.
Assuming updateParam action creator is correctly wrapped in call to dispatch in mapDispatchToProps in the connect HOC AND properly accessed from props in Component1, then I suggest checking/comparing props with previous props in componentDidUpdate and calling sortData if specifically the param prop value updated.
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
componentDidUpdate(prevProps) {
const { param } = this.props;
if (prevProps.param !== param) { // <-- if param prop updated, sort
this.sortData();
}
}
sortData = () => {
const { param } = this.props
let result = param.sort((a, b) => a - b));
// do something with result
}
}
mapStateToProps = state => ({
param: state.param,
});
connect(mapStateToProps)(Component2);
EDIT
Given component code from repository
let appointmentDates: object = {};
class Appointments extends React.Component<ApptProps> {
componentDidUpdate(prevProps: any) {
if (prevProps.apptList !== this.props.apptList) {
appointmentDates = {};
this.setAppointmentDates();
this.sortAppointmentsByDate();
this.forceUpdate();
}
}
setAppointmentDates = () => {
const { date } = this.props;
for (let i = 0; i < 5; i++) {
const d = new Date(
new Date(date).setDate(new Date(date).getDate() + i)
);
let month = new Date(d).toLocaleString("default", {
month: "long"
});
let dateOfMonth = new Date(d).getDate();
let dayOfWeek = new Date(d).toLocaleString("default", {
weekday: "short"
});
// #ts-ignore
appointmentDates[dayOfWeek + ". " + month + " " + dateOfMonth] = [];
}
};
sortAppointmentsByDate = () => {
const { apptList } = this.props;
let dates: string[] = [];
dates = Object.keys(appointmentDates);
apptList.map((appt: AppointmentQuery) => {
return dates.map(date => {
if (
new Date(appt.appointmentTime).getDate().toString() ===
// #ts-ignore
date.match(/\d+/)[0]
) {
// #ts-ignore
appointmentDates[date].push(appt);
}
return null;
});
});
};
render() {
let list: any = appointmentDates;
return (
<section id="appointmentContainer">
{Object.keys(appointmentDates).map(date => {
return (
<div className="appointmentDateColumn" key={date}>
<span className="appointmentDate">{date}</span>
{list[date].map(
(apptInfo: AppointmentQuery, i: number) => {
return (
<AppointmentCard
key={i}
apptInfo={apptInfo}
/>
);
}
)}
</div>
);
})}
</section>
);
}
}
appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function.
I wont to make an app which fetch some pics of NASA image of the day. I show the today's pic and some (4 for example) previous. I use datepicker to choose the image of the day of date by my choice. The problem is sometimes it work fine, sometimes shows only the today's photo, sometimes today's plus one or two previous. Can someone explain what's going on ?
I've reset the cookies, try it with Firefox and Chromium. I upload the code with DEMO_KEY but in my app use the key received after registration.
App.js:
import React, { Component } from "react";
import DateInput from "./components/DateInput.js";
import Photo from "./components/Photo.js";
import Axios from "axios";
class App extends Component {
state = {
date: new Date(),
currentPhoto: "",
photos:[]
};
componentDidMount(){
Axios
.get(`https://api.nasa.gov/planetary/apod?&api_key=DEMO_KEY`)
.then(response => this.setState({currentPhoto: response.data}));
this.getImages(5);
}
getImages = n => {
const daysBuffer = [];
for(let i=1; i<n; i++){
let today = new Date();
today.setDate(today.getDate()-i);
daysBuffer.push(today);
}
const picBuffer = [];
const datesBuffer = daysBuffer.map(day => this.getDate(day));
datesBuffer.map(date => {
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => picBuffer.push(response.data));
})
this.setState({photos: picBuffer});
}
getDate = time => {
let year = time.getFullYear();
let month = time.getMonth();
let day = time.getDate();
return (
`${year}-${month}-${day}`
)
};
getPhoto = a => {
let date = this.getDate(a);
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => this.setState({currentPhoto: response.data}))
}
changeDate = date => {
this.setState({
date
});
this.getPhoto(date);
}
render() {
const imageGrid = this.state.photos.map(pic => {
return (
<ul>
<Photo photo = {pic} key={pic.date} />
</ul>
)
})
return (
<div>
<h1>NASA's Astronomy Picture of the Day</h1>
<DateInput
changeDate = {this.changeDate}
date = {this.state.date}
/>
<Photo photo = {this.state.currentPhoto} />
{imageGrid}
</div>
);
}
}
export default App;
DateInput.js:
import React from "react";
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
const DateInput = props => (
<div>
Select a Date:
<DatePicker
selected = {props.date}
onChange = {props.changeDate}
/>
</div>
);
export default DateInput;
Photo.js
import React from 'react';
const Photo = props => (
<div>
<h3>{props.photo.title}</h3>
<img src={props.photo.url} alt={props.photo.title} />
<p>{props.photo.explanation}</p>
</div>
)
export default Photo;
The most likely problem in your code, is that you are taking synchronous action while you are retrieving images asynchronously.
The main problem lies in your getImages function
getImages = n => {
const daysBuffer = [];
for(let i=1; i<n; i++){
let today = new Date();
today.setDate(today.getDate()-i);
daysBuffer.push(today);
}
const picBuffer = [];
const datesBuffer = daysBuffer.map(day => this.getDate(day));
datesBuffer.map(date => {
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => picBuffer.push(response.data));
})
this.setState({photos: picBuffer}); //this line runs before Axios finishes
}
To fix this, without moving to async/await (which is better but requires restructuring), you would have to change the last few lines to this:
datesBuffer.map(date => {
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => {
picBuffer.push(response.data);
this.setState({photos: picBuffer});
})
})
Notice that it is now setting the state many times, which is not ideal, but without knowing the capabalities of Axios regarding async/await this would be the most logical solution.
I am fetching data from an API. I am building an array of 5 objects using the API call. What I am trying to do is iterate over the array, use the data inside each array index to build a component and pass along the props to another component.
I've tried accessing the element the same way I normally would by doing:
img={pokemon.name} but it keeps returning undefined. When I type in
console.log(pokemon) I get the individual pokemon stored within the array of objects.
import React, { Component } from "react";
import Pokecard from "./Pokecard";
async function getPokemon() {
const randomID = Math.floor(Math.random() * 151) + 1;
const pokeRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomID}/`);
const pokemonJSON = await pokeRes.json();
return pokemonJSON;
}
function buildPokemon() {
let pokemonArr = [];
let builtPokemon = {};
getPokemon()
.then(data => {
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
pokemonArr.push(builtPokemon);
})
.catch(err => console.log(err));
return pokemonArr;
}
class Pokedex extends Component {
constructor(props) {
super(props);
this.state = { pokemonArr: [] };
}
componentDidMount() {
const pokemonArr = [];
for (let i = 0; i < 5; i++) {
pokemonArr.push(buildPokemon());
}
this.setState({ pokemonArr });
}
render() {
console.log(this.state.pokemonArr);
return (
<div className="Pokedex">
{this.state.pokemonArr.map(pokemon => console.log(pokemon))}
</div>
);
}
}
export default Pokedex;
What should happen is that when I map the pokemonArr I want to create 5 separate pokemon by doing
this.state.pokemonArr.map(pokemon => <Pokecard name={pokemon.name} but I keep getting undefined whenever I check this.props in the Pokecard component.
I think my buildPokemon() function is working because when I call it in the componentDidMount() and then I console.log this.state.pokemonArr in the render() function, I actually get an array returned with 5 different pokemon with the proper fields filled out.
And also when I map out this.state.pokemonArr.map(pokemon => clg(pokemon)), it actually displays each individual pokemon. When I pass the pokemon item into a component like this
<Pokecard name={pokemon}/>, I see all the pokemon data.
when I type <Pokecard name={pokemon.name} I get undefined
There are several problems with your approach but the main one is that getPokemon() is asynchronous.
Return the getPokemon() promise from buildPokemon() and return the object from it's then()
In your for() loop create an array of these promises and use Promise.all() to set state once they have all resolved
function buildPokemon() {
let builtPokemon = {};
// return the promise
return getPokemon()
.then(data => {
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
// return the object
return builtPokemon
});
}
componentDidMount() {
const pokemonPromises = [];
for (let i = 0; i < 5; i++) {
pokemonPromises.push(buildPokemon());
}
Promise.all(pokemonPromises).then(pokemonArr => this.setState({ pokemonArr }));
}
componentDidMount executes after first render, initially your state is pokemonArr: [] (whch is empty) so you are getting an error. You need to conditionally render like,
{this.state.pokemonArr.length > 0 && this.state.pokemonArr.map(pokemon => console.log(pokemon))}
Side Note:
In buildPokemon function you are returning an array, and again in componentDidMount you are storing it in array which creates array of array's, you just need to return object from buildPokemon function.
The problem is mainly how the Promise should be resolved.
The data isn't available right away so the state (pokemonArr) should only be set once data is available.
Here's the refactored component:
class Pokedex extends Component {
constructor(props) {
super(props);
this.state = { pokemonArr: [] };
}
componentDidMount() {
for (let i = 0; i < 5; i++) {
this.getPokemon()
.then((pokemon) => this.buildPokemon(pokemon));
}
}
async getPokemon() {
const randomID = Math.floor(Math.random() * 151) + 1;
const pokeRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomID}/`);
return pokeRes.json();
}
setPokemon(pokemon) {
this.setState({
pokemonArr: [
...this.state.pokemonArr, pokemon
],
});
}
buildPokemon(data) {
let builtPokemon = {};
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
this.setPokemon(builtPokemon);
}
render() {
return (
<div className="Pokedex">
{this.state.pokemonArr.map(pokemon => console.log(pokemon))}
</div>
);
}
}
I'm learning reactjs and I'm stuck calling a function in another component.
I did:
import moment from 'moment';
import WeatherLocation from './../components/WeatherLocation'
const transformForecast = datos =>(
datos.list.filter(item => (
moment.unix(item.dt).utc().hour() === 6 ||
moment.unix(item.dt).utc().hour() === 12 ||
moment.unix(item.dt).utc().hour() === 18
)).map(item => (
{
weekDay: moment.unix(item.dt).format('ddd'),
hour: moment.unix(item.dt).hour(),
data: WeatherLocation.getDatos(item)
}
))
);
export default transformForecast;
getDatos is a function in WeatherLocation, I exported WeatherLocation but I don't know what if that calling is correct.
WeatherLocation component:
const api_key = "bb7a92d73a27a97e54ba00fab9d32063";
class WeatherLocation extends Component{
constructor({ city }){
super();
this.state = {
city,
primero: null
}
}
getWeatherState = weather => {
const { id } = weather[0];
if (id < 300){
return THUNDER;
}else if (id < 400){
return DRIZZLE;
}else if (id < 600){
return RAIN;
}else if (id < 700){
return SNOW;
}else if (id >= 800){
return SUN;
}else{
return CLOUDY;
}
};
getTemp = kelvin =>{
return convert(kelvin).from('K').to('C').toFixed(2);
}
getDatos = (weather_data) =>{
const {weather} = weather_data;
const {humidity, temp} = weather_data.main;
const {speed} = weather_data.wind;
const weatherState = this.getWeatherState(weather);
const temperature = this.getTemp(temp);
const primero = {
humidity,
temperature,
weatherState,
wind: `${speed}`,
}
return primero;
};
componentWillMount() {
this.handleUpdateClick();
}
handleUpdateClick = () => {
const {city} = this.state;
const urlTiempo = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${api_key}`;
fetch(urlTiempo).then(primero => {
return primero.json();
}).then(weather_data => {
const primero = this.getDatos(weather_data);
this.setState({primero});
});
};
render = () => {
const {onWeatherLocationClick} = this.props;
const {city, primero} = this.state;
return (
<div className='weatherLocationCont' onClick = {onWeatherLocationClick}>
<Location city={city}/>
{primero ? <WeatherData datos = {primero}/> : <CircularProgress size={60} thickness={7} />}
</div>);
};
}
WeatherLocation.propTypes = {
city: PropTypes.string.isRequired,
onWeatherLocationClick: PropTypes.func
}
export default WeatherLocation;
As you can see I want to reuse getDatos because I'm going to need those variable in transformForecast.
I will appreciate your help, thanks.
WeatherLocation is a React component, not a plain JS object, so you can't just call its internal functions as you please: as just a class definition there is nothing to call yet, you need an instance.
So, you'll need to create an actual <WeatherLocation.../> component on your page/in your UI, and then use the WeatherLocation's documented API for getting its data based on changes in the component, passing it on to whatever is calling the transformForecast function.
Object.Method() call is not allowed here. You need to create a React Stateful or Stateless component and pass props to it from the parent component. Let us say, WeatherLocation is your parent component and transformForecast is the child component. You can do something like this to call your method in WeatherLocation component.
Parent Component:
class WeatherLocation extends React.Component {
constructor(props) {
super(props);
this.state = {
datos: []
};
this.getDatos = this.getDatos.bind(this);
};
getDatos = (item) => {
console.log(item);
};
render() {
return (
<div>
<TransformForecast
getDatos={this.getDatos}
datos={this.state.datos}
/>
</div>
)
}
}
export default WeatherLocation;
Child Component:
const TransformForecast = (props) => {
return (
props.datos.list.filter(item => (
moment.unix(item.dt).utc().hour() === 6 ||
moment.unix(item.dt).utc().hour() === 12 ||
moment.unix(item.dt).utc().hour() === 18
)).map(item => (
{
weekDay: moment.unix(item.dt).format('ddd'),
hour: moment.unix(item.dt).hour(),
data: props.getDatos(item)
}
))
);
};
export default TransformForecast;
Note: This code might not be the right away working code as I'm not sure of the data and API's called. This is just to illustrate to solve your problem.
Hope this helps.
I'm running into an issue where my console is telling me setState is not defind. I'm not sure what I'm doing wrong. I preload the my object (data) in the constructor. I then pass it through to a table where it is displayed. When a user then selects an option from a dropdown it should only show the filtered data. I've bound the function in the constructor so I'm not sure what I've done wrong. Code is below:
import React, {Component} from ‘react’;
import moment from ‘moment’;
import {Table} from ‘reactstrap’;
import classnames from ‘classnames’;
class Tasks extends Component {
constructor(props){
super(props);
this.state = {
data: [
{
“task_number”:””,
“work_end”:”10/01/2017”
},
{
etc…
}
]
}
this.checkDate = this.checkDate.bind(this);
}
checkDate(e) {
let d = moment.format(“MM/DD/YYYY”);
let currentData = this.state.data;
currentData.filter(function(newData){
if(newData.work_end < d){
//comes back as undefined
this.setState({data: newData});
}
});
}
render(){
const { data } = this.state;
let myData = data.map((header, i){
return (<tr key = {i}>
<td>{data[i].task_number}</td>
<td>{data[i].work_end}</td>
</tr>)
});
return(
<div>
<table>
{myData}
</table>
<Input type=“select”
name=“assigngroup”
id=“assigngroup”
value={this.state.value}
onChange={this.checkDate}
/>
</div>
);
}
}
export default Tasks;
I've also tried creating a new function and passing it from checkDate, but that failed as well.
In Javascript, functions have their own scope so in checkDate this is your component but, inside the function that you use to filter your currentData, this is something else.
Your code should go like this:
currentData.filter(newData => {
if(newData.work_end < d){
this.setState({data: newData});
}
});
or just save this in a variable:
var self = this;
currentData.filter(function(newData){
if(newData.work_end < d){
self.setState({data: newData});
}
});
The filter method returns a new filtered array.
Try this:
checkDate(e) {
let d = moment().format(“MM/DD/YYYY”)
let currentData = this.state.data
let filteredData = currentData.filter(item => item.work_end < d)
this.setState({ data: filteredData })
}