Inserting a variable inside weather app link using Axios - reactjs

I am using a weather app API to create a weather application.
I was able to reach the point that I could've print the weather if I hard coded the country in the link given.
My aim was to :
1 - Print the value of the city when I write it in the input and take the value and insert it in the link.
2 - unfortunately I couldn't do that [the point i reached is that I could get the input, but it won't accept it in the link]
Hint: I am a React beginner and trying my best to make my code work and using the DRY principle.
I appreciate your time and could use your help here.
Thanks in advance.
//Importing
import React, { Component } from "react";
import axios from "axios";
//syntax
let inputValue = "";
console.log(" input value before updated", inputValue);
export default class main extends Component {
state = {
showList: false,
data: "",
temp: "",
feels_like: "",
humidity: "",
pressure: "",
name: "",
country: "",
description: "",
icon: "",
weather: "",
};
//Component Did mount
handleInput = (event) => {
this.setState({
data: event.target.value,
});
};
submitted = () => {
inputValue = this.state.data;
const show = this.state.showList;
this.setState({ showList: !show });
};
componentDidMount() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?q=${inputValue},uk&APPID=xxxxxxxxxxxxxxxxx`
)
.then((response) => {
this.setState({
temp: response.data.main.temp,
feels_like: response.data.main.feels_like,
humidity: response.data.main.humidity,
pressure: response.data.main.pressure,
name: response.data.name,
country: response.data.sys.country,
description: response.data.weather[0].description,
icon: response.data.weather[0].icon,
weather: response.data.weather[0].main,
});
})
.catch((error) => console.log(error));
}
render() {
let feels_like = this.state.feels_like;
let temp = this.state.temp;
let humidity = this.state.humidity;
let pressure = this.state.pressure;
let name = this.state.name;
let country = this.state.country;
let description = this.state.description;
let icon = this.state.icon;
let weather = this.state.weather;
let list = null;
if (this.state.showList) {
list = (
<div>
<h1>{this.state.data}</h1>
<h1>{feels_like}</h1>
<h1>{temp}</h1>
<h1>{humidity}</h1>
<h1>{pressure}</h1>
<h1>{name}</h1>
<h1>{country}</h1>
<h1>{description}</h1>
<img
alt=""
src={`http://openweathermap.org/img/wn/${icon}.png`}
></img>
<h1>{weather}</h1>
</div>
);
}
return (
<div>
<input
onChange={this.handleInput}
typw="text"
placeholder="Enter a country name"
/>
<button onClick={this.submitted}>Search</button>
{list}
</div>
);
}
}
Extra Info:
.get(
`http://api.openweathermap.org/data/2.5/weather?q=${inputValue},uk&APPID=xxxxxxxxxxxxxxxxx`
)
This is the part where I got 404 since it is not accepting the ${inputValue}

Make the inputValue part of the state. There is no reason to keep it as a global variable outside of the component like that.

Related

Why is this not rendering from a map, in react

So I get items from a DB and console.log them just before, so I know they exist and are in the format I want them to be. I also wrote a simple method to make sure rendering works. The code looks something like this (is simplified for nonrelevantbits)
const renderSomething = () => {
getFromDB().then(function(data){
if (data) {
console.log(data.something) //returns an array in console
return data.something.map(somet => {
console.log(' somet is ' + somet) //return somet with quotes: ""
return <p>{somet}</p>
})
} else {
console.log("No data!");
}
}
}
and then in the return:
return (
<div>
{renderSomething()}
</div>
)
And nothing appears on the screen. I made a test one to make sure it should:
const basic = () => {
return ['abc', 'bca'].map(num => <h1>{num}</h1>)
}
return (
<div>
{basic()}
</div>
)
The test one worked
Render function cannot be a promise. You should useEffect method to downlaod the data from api and then useState to set it for the component.
import React, { Fragment, useState, useEffect } from "react";
import ReactDOM from "react-dom";
function LiveVisitors() {
const [visitors, setVisitors] = useState([
{
ip: "",
countryCode: "",
city: "Warsaw",
state: "",
country: "Poland"
},
{
ip: "",
countryCode: "",
city: "Gdańsk",
state: "",
country: "Poland"
}
]);
const { ip, countryCode, city, state, country } = visitors;
useEffect(() => {
getUserData();
}, []);
const getUserData = () => {
//imitate fetch
setVisitors([
...visitors,
{
ip: "",
countryCode: "",
city: "Wrocław",
state: "",
country: "Poland"
}
]);
};
return (
<div>
{visitors.map((el) => (
<div>{el.city}</div>
))}
</div>
);
}
const wrapper = document.getElementById("container");
ReactDOM.render(<LiveVisitors />, wrapper);
Here is the interactive version where you can play with it.
https://codesandbox.io/s/react-playground-forked-hqswi?file=/index.js:0-947

How to use prevState so I don't have to use a setTimeout

How to use prevState on 'this.state.lat' & 'this.state.lng' so that I can remove the setTimeout from this peice of code below:
getCords(authUser) {
setTimeout(() => {
this.props.firebase.cats().push({
text: this.state.text,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9',
userId: authUser.uid,
lat: this.state.lat,
lng: this.state.lng,
})
this.setState({ ...INITIAL_STATE });
}, 150)
}
Without the setTimeout I get the incorrect values, so I think using prevState in replace of setTimeout should fix this issue? Any pointers are much appreciated!
Full code:
import React from 'react';
import { AuthUserContext, withAuthorization } from '../Session';
import Geocode from "react-geocode";
const INITIAL_STATE = {
text: '',
lat: '',
long: '',
address: ''
}
class AddCat extends React.Component {
constructor(props) {
super(props);
this.state = {
...INITIAL_STATE
}
}
componentDidMount() {
// set Google Maps Geocoding API for purposes of quota management. Its optional but recommended.
Geocode.setApiKey(process.env.REACT_APP_GOOGLEGEOCODEKEY);
// set response language. Defaults to english.
Geocode.setLanguage("en");
// set response region. Its optional.
// A Geocoding request with region=es (Spain) will return the Spanish city.
Geocode.setRegion("es");
// Enable or disable logs. Its optional.
Geocode.enableDebug();
// Get latidude & longitude from address.
}
getCords(authUser) {
setTimeout(() => {
this.props.firebase.cats().push({
text: this.state.text,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9',
userId: authUser.uid,
lat: this.state.lat,
lng: this.state.lng,
})
this.setState({ ...INITIAL_STATE });
}, 150)
}
onCreateCat = (e, authUser) => {
Geocode.fromAddress(this.state.address).then(
response => {
const { lat, lng } = response.results[0].geometry.location;
this.setState({lat: lat, lng: lng},
this.getCords(authUser)
);
},
error => {
console.error(error);
}
)
e.preventDefault();
}
onChangeText = e => {
this.setState({ text: e.target.value });
};
onChangeAddress = e => {
this.setState({ address: e.target.value });
};
render() {
console.log(this.state);
return (
<div>
<h1>Add cat</h1>
<AuthUserContext.Consumer>
{authUser => (
<div>
<form onSubmit={e => this.onCreateCat(e, authUser)}>
<input
type="text"
value={this.state.text}
onChange={this.onChangeText}
placeholder="Cats Name"
/>
<input
name="address"
value={this.state.address}
onChange={this.onChangeAddress}
type="text"
placeholder="Cats Postcode">
</input>
<button type="submit">Send</button>
</form>
</div>
)}
</AuthUserContext.Consumer>
</div>
);
}
}
const condition = authUser => !!authUser;
export default withAuthorization(condition)(AddCat);
If you look at the signature of the setState method it takes an updater and a callback that will run after the state gets updated:
setState(updater[, callback])
So you could call your this.getCords function inside of a this.setState callback, like so:
onCreateCat = (e, authUser) => {
e.preventDefault()
Geocode.fromAddress(this.state.address).then(
response => {
// ...
this.setState({ lat, lng }, () => {
// call methods of your component
// that rely on latest state values
// for `lat` and `lng` here
this.getCords(authUser)
})
}
)
}
Then, inside of the getCords method, you'd just do whatever you need to do without introducing "hacks":
getCords = (authUser) => {
this.props.firebase.cats().push(...)
this.setState({ ...INITIAL_STATE })
}
Here's a quick demo:
CodeSandbox
Hope this helps.

Geocoding API - address not updating to lat long instead persisting to database as postcode text

I am trying to use the Google geocode api to turn a postcode into lat long coordinates.
It seems to be working when I console.log.state, however the postcode text i.e 'TW135QZ' is being persisted into the database instead of the lat long coordinates.
Can anybody see why the lat long cords are not being persisted? I am stuck now >.<
Main bit of code:
onCreateCat = (e, authUser) => {
Geocode.fromAddress(this.state.address).then(
response => {
const { lat, lng } = response.results[0].geometry.location;
this.setState({address: lat + ',' + lng});
},
error => {
console.error(error);
}
)
.then(
this.props.firebase.cats().push({
text: this.state.text,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9',
userId: authUser.uid,
address: this.state.address,
})
)
e.preventDefault();
}
Fullcode:
const INITIAL_STATE = {
text: '',
address: ''
}
class AddCat extends React.Component {
constructor(props) {
super(props);
this.state = {
...INITIAL_STATE
}
}
componentDidMount() {
// set Google Maps Geocoding API for purposes of quota management. Its optional but recommended.
Geocode.setApiKey(process.env.REACT_APP_GOOGLEKEY);
// set response language. Defaults to english.
Geocode.setLanguage("en");
// set response region. Its optional.
// A Geocoding request with region=es (Spain) will return the Spanish city.
Geocode.setRegion("es");
// Enable or disable logs. Its optional.
Geocode.enableDebug();
// Get latidude & longitude from address.
}
onCreateCat = (e, authUser) => {
Geocode.fromAddress(this.state.address).then(
response => {
const { lat, lng } = response.results[0].geometry.location;
this.setState({address: lat + ',' + lng});
},
error => {
console.error(error);
}
).then(
this.props.firebase.cats().push({
text: this.state.text,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9',
userId: authUser.uid,
address: this.state.address,
})
)
e.preventDefault();
}
onChangeText = e => {
this.setState({ text: e.target.value });
};
onChangeAddress = e => {
this.setState({ address: e.target.value });
};
render() {
const { text, address } = this.state;
console.log(this.state);
return (
<div>
<h1>Add cat</h1>
<AuthUserContext.Consumer>
{authUser => (
<div>
<form onSubmit={e => this.onCreateCat(e, authUser)}>
<input
type="text"
value={text}
onChange={this.onChangeText}
placeholder="Cats Name"
/>
<input
name="address"
value={address}
onChange={this.onChangeAddress}
type="text"
placeholder="Cats Postcode">
</input>
<button type="submit">Send</button>
</form>
</div>
)}
</AuthUserContext.Consumer>
</div>
);
}
}
const condition = authUser => !!authUser;
export default withAuthorization(condition)(AddCat);

how to push a new element into an array from the state

I'm trying to push in elements into an array called 'this.state.tags'. On the console, I see the elements pushing into the array. However, when I add something, the array comes out blank, when I add more items I only the see the previous items I've added. I'm not seeing the newest item I've pushed in.
I've done Object.assign([], this.state.tags) from the child component Grades.js. Then I pushed in 'this.state.newTag' and I've reset the state to that new result.
//From Grades.js, the child component
state = {
toggle: null,
newTag: '',
tags: []
}
addTags = (event) => {
event.preventDefault();
let newTagArr = Object.assign([], this.state.tags)
newTagArr.push(this.state.newTag)
this.setState({
tags: newTagArr
})
// this will pass on to the parent
this.props.filterTags(this.state.tags)
}
render() {
const { tags } = this.state
let tagList = tags.map((item, index) => {
return (
<li key={index} className="tagListItem">{item}</li>
)
})
return(
<div>
<ul className="tagList">{tagList}</ul>
<form onSubmit={this.addTags}>
<input
placeholder="Add a tag"
name="newTag"
onChange={this.handleInput}
style={{border: '0', borderBottom: '1px solid #000'}}
/>
</form>
</div>
)
}
// From App.js, the parent component
state = {
students: [],
filteredStudents: [],
filteredByTag: [],
search: '',
tag: '',
toggle: false
}
componentDidMount() {
axios.get('https://www.hatchways.io/api/assessment/students')
.then(result => {
let newArr = Object.assign([], result.data.students);
let newResult = newArr.map(elem => {
return {city: elem.city, company: elem.company, email: elem.email,
firstName: elem.firstName.toLowerCase(), lastName: elem.lastName.toLowerCase(),
grades: elem.grades, id: elem.id, pic: elem.pic, skill: elem.skill}
})
this.setState({
students: newResult
})
})
.catch(err => console.log(err))
}
tagFiltering = (param) => {
console.log(param)
this.state.students.push()
}
I expect the output to be ["tag1", "tag2", "tag3"]
Not ["tag1", "tag2"], when I've already typed in tag1, tag2 and tag3
Use ES2015 syntax :
this.setState({
tags: [...this.state.tags , this.state.newTag]
})
In react the state is immutable meaning that we have to provide new state object every time, we call the setState method.

How to clear input text box, after obtaining the result from API in React

I need help with my React weather app. I am searching for a city to obtain a weather forecast from API. Everything is working. When I enter the city name and search, result is appearing. But I want to clear the input text when the result is obtained. I did the following resetForm, It is clearing the input field but at the same time clearing the result from API.
class Form extends React.Component{
render(){
resetForm = () => {
this.refs.inputId.value="";
}
return(
<form onSubmit={this.props.getWeather}>
<input type="text" name="city" placeholder="Type a city name..." ref="inputID" />
<button>Search</button>
</form>
);
}
};
getWeather = async (e) =>{
this.setState({loading: true});
e.preventDefault();
const city = e.target.elements.city.value;
const api_call = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`);
const data = await api_call.json();
if(city){
console.log(data);
this.setState({
temperature: data.main.temp,
city: data.name,
humidity: data.main.humidity,
description: data.weather[0].description,
error: "",
loading:false,
});
}else{
this.setState({
temperature: undefined,
city: undefined,
humidity: undefined,
description: undefined,
error: "Please enter the city and the country name!",
loading:false,
});
}
Controlled Components is the simplest way to do this.
In parent component, you need to maintain a state variable and change handler function and pass it to the Form component,
state = {
inputCity: '' //You can add this in your existing state
}
And the change handler function,
onCityChange = (e) => {
this.setState({inputCity : e.target.value})
}
Now you caan pass them to your Form component,
<Form inputCity={this.state.inputCity} onCityChange={this.onCityChange} getWeather={this.getWeather}/>
You Form component should be,
class Form extends React.Component{
render(){
return(
<form onSubmit={this.props.getWeather}>
<input type="text" name="city" placeholder="Type a city name..." value={this.props.inputCity} onChange={this.props.onCityChange}/>
<button>Search</button>
</form>
);
}
};
Your getWeather function should be,
getWeather = async (e) =>{
this.setState({loading: true});
e.preventDefault();
const city = this.state.inputCity; //take the city from state directly
const api_call = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`);
const data = await api_call.json();
if(data){ //instead of city it should be data here
console.log(data);
this.setState({
temperature: data.main.temp,
city: data.name,
humidity: data.main.humidity,
description: data.weather[0].description,
error: "",
loading:false,
inputCity: "" //here you can clear the city
});
}else{
this.setState({
temperature: undefined,
city: undefined,
humidity: undefined,
description: undefined,
error: "Please enter the city and the country name!",
loading:false,
});
}
Try this: I have modified your code a little to use state, ref is fine but is usually considered not the most efficient approach:
this.setState has a callback as well since it's an async operation, you can make your API request after loading is set to true, after the response is successful or fails (as soon as the response from the server is recieved), set the loading state to false and clear the input.
The rest of your code is fine, except for a few tweaks, it will now work.
Read more about controlled inputs here: https://reactjs.org/docs/forms.html
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class Form extends React.Component{
state = {
loading:false,
temperature: '',
city: '',
humidity: '',
description: '',
error: '',
cityName: ''
}
handleChange = (e) => {
this.setState({ city: e.target.value });
}
getWeather = (e) => {
e.preventDefault();
this.setState({loading: true}, async () => {
const {city} = this.state;
const api_call = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`);
const data = await api_call.json();
if(data){
console.log(data);
this.setState({
temperature: data.main.temp,
cityName: data.name,
humidity: data.main.humidity,
description: data.weather[0].description,
error: "",
loading:false,
}, () => {
this.setState({ city: '' })
});
}else{
this.setState({
temperature: undefined,
cityName: undefined,
humidity: undefined,
description: undefined,
error: "Please enter the city and the country name!",
loading:false,
}, () => {
this.setState({ city: '' })
});
}
});
}
render(){
return(
<form onSubmit={this.getWeather}>
<input type="text"
name="city"
value={this.state.city}
placeholder="Type a city name..."
onChange={this.handleChange} />
<button>Search</button>
</form>
);
}
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Form />, rootElement);
Sandbox link: https://codesandbox.io/s/zen-browser-e2rh8?fontsize=14

Resources