First stackoverflow post. New to React (1 week) and using API fetch requests to understand props, state and components.
I'm using a weather API to get the temperature for a particular city. User enters city into a form, clicks submit and the temperature is displayed.
When I hard code the selectedCity state by typing in a city as a test, I can see in the DevTools that the API data is fetched for that city and the city's temperature is displayed in the browser. The issue is when I click submit on my form component. On submit I can see in the DevTools that it updates the selectedCity state with user's city of choice but it doesn't fetch the data. I've noticed that when hard coding in the city that the page refreshes and the data is fetched and result displayed but when submitted via the form there is no refresh of the page.
I just don't know enough about React to figure out what is going on here. Grateful for any pointers.
This is my WeatherContainer
import React, { Component } from 'react';
import Headings from '../components/Headings';
import Form from '../components/Form';
import Weather from '../components/Weather';
class WeatherContainer extends Component {
constructor(props) {
super(props);
this.state = {
weatherData: [],
selectedCity: ""
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
fetchData() {
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${this.state.selectedCity}&APPID=MyAPIKey`)
.then(res => res.json())
.then(result => this.setState({ weatherData: result.main }))
}
componentDidMount() {
this.fetchData();
}
handleFormSubmit({city}) {
this.setState({selectedCity: city})
}
render() {
return (
<div>
<Headings />
<Form onFormSubmit={this.handleFormSubmit} />
<Weather weather={this.state.weatherData} />
</div>
);
}
}
export default WeatherContainer;
This is my form component
import React, { Component } from 'react';
class Form extends Component {
constructor(props) {
super(props);
this.state = {
city: ""
};
this.handleCityChange = this.handleCityChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
const city = this.state.city;
if (!city) {
return
}
this.props.onFormSubmit({
city: city
});
this.setState({
city: ''
})
}
handleCityChange(event) {
this.setState({
city: event.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.city}
placeholder="Enter City"
onChange={this.handleCityChange}
/>
<input type="submit" value="Submit" />
{/* <button>Get Weather</button> */}
</form>
);
}
}
export default Form;
My Weather Component
import React from 'react';
const Weather = ({ weather }) => {
if(!weather) return null
return (
<div>
<h1>{weather.temp}</h1>
</div>
);
}
export default Weather;
So you have a componentDidMount method but you don't handle the updates of the state.
You need a componentDidUpdate method
componentDidUpdate(prevProps, prevState) {
if (prevState.city !== this.state.city) {
this.fetchData();
}
}
Related
class App extends Component{
constructor(props){
super(props);
this.state = {
users:[]
};
}
componentDidMount() {
axios.get(`http://localhost:3000/employees`)
.then(res => {
const users = res.data;
this.setState({ users });
})
}
render(){
return(
<div>
<Main users= {this.state.users}/>
<Form/>
</div>
);
}
}
class Main extends Component{
state = {
id: ''
}
handleChange = event => {
this.setState({ id: event.target.value });
}
handleSubmit = event => {
event.preventDefault();
axios.delete(`http://localhost:3000/employees/${this.state.id}`)
.then(res => {
console.log(res);
console.log("this is" + res.data);
})
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<label>
Person Name:
<input type="text" name="name" onChange={this.handleChange} />
</label>
<button type="submit">Delete</button>
</form>
</div>
)
}
}
Can someone tell me why after the Axios delete request, how I can render the new state in the users array from App component?
In the App component, I am trying to make this.state.users as a props to send it to the Form component. My guess is put this.setState({users: res.data}). The delete request is fine with 200, but I need to refresh the page to get the new result. How can I update immediatly?
// this is a json object
"employees": [
{
"id": 8,
"first_name": "Lteve",
"last_name": "Palmer",
"email": "steve#codingthesmartway.com"
},
As Dave mentions in a comment you want to have single responsibility for state between components.
This topic is also discussed in the blog post You Probably Don't Need Derived State, where one solution to your problem is to have Main "report back" to App in order to update state. So either App passes down a onDeleteUser function, or a callback for when a user is removed, such as onUserWasDeleted.
The latter can be done with the least amount of changes to your code I suppose.
class App extends Component {
constructor(props) {
super(props);
this.onUserWasDeleted = this.onUserWasDeleted.bind(this);
}
onUserWasDeleted(userId) {
// remove user that was successfully removed
this.setState({ users: this.state.users.filter(user => user.id !== userId) });
}
render() {
return (
<Main
users={this.state.users}
// pass down a callback to Main
onUserDeleted={this.onUserWasDeleted}
/>
);
}
}
class Main extends Component {
handleSubmit = event => {
event.preventDefault();
axios.delete(`http://localhost:3000/employees/${this.state.id}`)
.then(res => {
console.log(res);
console.log("this is" + res.data);
// call our callback function in App
this.props.onUserWasDeleted(this.state.id);
})
}
}
I'm creating a weather app in React using OpenWeatherMap API. There are input form and a button, and I'm expecting to see city name when I click the botton. I received data from the API when I do so, but can't render it on a screen while I can log it in a console.
For this, I'm using three separated files. App.js, Form.js for submitting terms, and weather.js for API configuration.
I'm guessing that I need to map the received data but not yet successful.
class App extends React.Component {
state = {
city: null,
}
getWeather = async city => {
const response = await weather.get('/forecast', {
params: {
q: city
}
});
this.setState({
city: response.name,
})
console.log(city); <--- This works
}
render() {
return (
<div>
<Form loadWeather={this.getWeather} />
<p>{this.state.city}</p> <--- This doesn't work
</div>
);
}
}
class Form extends React.Component {
state = { term: '' };
onFormSubmit = (event) => {
event.preventDefault();
this.props.loadWeather(this.state.term);
this.refs.textInput.value = '';
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
ref="textInput"
type="text"
value={this.state.term}
onChange={event => this.setState({term: event.target.value})}
/>
<button>Get Weather</button>
</form>
</div>
);
}
}
export default Form;
I'm going to pass the {this.state.name} as a prop to a child component, but so far the received data doesn't even appear on that component ifself.
"this.setState" is a function, should be called like this.
this.setState({
city: response.name,
})
You're setting state's city to response.name. You tagged the question as axios, so I'm assuming you're using axios for the ajax. If so, you'll want to get the data from the response back from response.data.name, not response.name.
If you want to get the city details from the initial render try calling/invoking the getWeather() method in componentDidMount() life cycle. The way you are using setState() is also wrong. it should be something like this as mentioned below. The same lifecyle could be used to get the data even though you have used a separate file to invoke the method
class App extends React.Component {
state = {
city: null
};
componentDidMount() {
this.getWeather("city");
}
getWeather = async city => {
const response = await weather.get("/forecast", {
params: {
q: city
}
});
this.setState({
city: response.name
});
};
render() {
return (
<div>
<p>{this.state.city}</p>
</div>
);
}
}
I am building a grid order tool for product display on a website. It takes in a CSV, parses it into JSON, allows the user to reorder it, and then saves the new CSV. Because I am saving the array object in state, I have two problems in one. After uploading a file, I managed to get the array object into state. There are two parts to this question.
1) How do I get state into the UI?
2) Then I will have to map through an array of objects
import React, { Component } from 'react'
import Papa from 'papaparse'
class Product extends Component {
constructor(props) {
super(props);
this.state = {data: [] };
this.handleChange = this.handleChange.bind(this);
this.updateData = this.updateData.bind(this)
}
handleChange(event) {
event.preventDefault()
const inventory = event.target.files[0]
Papa.parse(inventory, {
header: true,
complete: this.updateData
})
} // END
updateData(results) {
const data = results.data
console.log(data)
this.setState({data}) // I have it in state. How to get it in UI?
}
render() {
return (
<div>
<form >
<label>
Upload file:
<input type="file" onChange={this.handleChange} />
</label>
</form>
<div> Map through state here </div>
</div>
);
}
} // END
export default Product
You can create a method that maps your collection and returns an array of UI components. Then render them in your component's return statement.
import React, { Component } from 'react'
import Papa from 'papaparse'
class Product extends Component {
constructor(props) {
super(props);
this.state = {data: [] };
this.handleChange = this.handleChange.bind(this);
this.updateData = this.updateData.bind(this);
this.renderData = this.renderData.bind(this);
}
handleChange(event) {
event.preventDefault()
const inventory = event.target.files[0]
Papa.parse(inventory, {
header: true,
complete: this.updateData
})
} // END
updateData(results) {
const data = results.data
console.log(data)
this.setState({data}) // I have it in state. How to get it in UI?
}
// method that check data prop in state for items in array,
// returning a collection of UI components if there is,
// otherwise returns null
renderData() {
return this.state.data.length > 1
? this.state.data.map((item) => (
<div>{item.title}</div> // assuming your data item has a title prop
))
: null;
}
render() {
return (
<div>
<form >
<label>
Upload file:
<input type="file" onChange={this.handleChange} />
</label>
</form>
<div>
{/* execute method here */}
{this.renderData()}
</div>
</div>
);
}
} // END
export default Product
I have a React container called UserContainer which renders a component called UserComponent.
The code looks approximately like this (I have removed the unnecessary bits):
// **** CONTAINER **** //
class UserContainer extends React.Component<ContainerProps, ContainerState> {
state = { firstName: "placeholder" };
async componentDidMount() {
const response = await this.props.callUserApi();
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
private isChanged(componentState: ComponentState) {
return this.state.firstName === componentState.firstName;
}
async save(newValues: ComponentState) {
if (!this.isChanged(newValues)) {
console.log("No changes detected.");
return;
}
const response = await this.props.changeFirstName(newValues.firstName);
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
render() {
return <UserComponent firstName={this.state.firstName} onSave={(newValues: ComponentState) => this.save(newValues)} />;
}
}
export default UserContainer;
// **** COMPONENT **** //
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
export default UserComponent;
The problem is that this.state.firstName in the component is always "placeholder". Even after the container gets its values from the API, the state of the component is not changed (however, the props are changed). When adding console.log into the individual methods, the flow of individual steps is following:
Container render()
Component constructor()
Component render()
Container didMount()
Container render()
Component render()
As you can see, the component constructor is called just once, prior to the container receiving its data from the backend API. Is there a way to pass the updated container state into the component in order to display the real data?
There are really FEW cases where updating state by props is necessary, I suggest you to read the full blog post from facebook under paragraph "Preferred Solutions": https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
componentWillReceiveProps(nextProps: ComponentProps){
if(nextProps.firstName != this.props.firstName){
this.state = { firstName: nextProps.firstName }
}
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
For latest React version please use getDerivedStateFromProps
You are already passing the updated data to the component. Only mistake is, you are assigning it once. So, whenever you get the updated values, it doesn't reflect, since you don't have only assigned it once.
Two ways to go about it.
If there is no manipulation taking place. Change this.state.firstName to this.props.firstName
<input type="text" value={this.props.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
If there is some manipulation taking place, you'll be doing it in the componentWillReceiveProps method and then setting your firstName state. This method will be triggered whenever you'll be updating the states.
Example -
componentWillReceiveProps(nextProps) {
if(this.props.firstName!==nextProps.firstName) {
//do your validation
}
}
EDIT
As dubes rightly pointed out, componentWillReceiveProps method is deprecated. So you'll have to use the static getDerivedStateFromProps and have to return the new resulting state from this method.
Hope this helps :)
I am trying to display school data from an external api using React. I'm just trying to display a school name to start. The school name appears in the console, but it doesn't show up in the browser.The api call is correct, as it works in Postman. Here is my code:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
schoolName: '',
// schoolData: {}
}
}
fetchSchool(event) {
event.preventDefault();
const apiKey = 'XdOHSc8fKhMKidPu2HWqCZmMy9OxtCJamGC580Bi';
const fields = `_fields=school.name,2015.aid.median_debt.completers.overall,2015.cost.tuition.in_state&school.name=${this.state.schoolName}`;
const requestUrl = `https://api.data.gov/ed/collegescorecard/v1/schools?&api_key=${apiKey}&${fields}`;
const school = fetch(requestUrl).then((res) => res.json()).then((data) => console.log(data.results[0]['school.name']));
this.setState({
schoolName: school
// schoolData: school
})
console.log(this.state.schoolName);
}
setSchool(event) {
event.preventDefault();
this.setState({
schoolName: event.target.value
});
}
render() {
// const schoolname = this.state.schoolName[0];
// const {schooName} = this.state;
return (
<div>
<form action="/school" method="GET" id="myform">
<input type="text" className="form-control" id="enter_text" onChange={this.setSchool.bind(this)} />
<button onClick={this.fetchSchool.bind(this)} type="submit" className="btn btn-primary" id="text-enter-button button submit">Submit</button>
</form>
<div>
<p>School: {this.state.school} </p>
</div>
</div>
);
}
}
export default App;
fetch is asynchronous. Therefore, setState is being called before the data has been fetched.
To fix this, call this.setState from inside of your then function
const school = fetch(requestUrl)
.then((res) => res.json())
.then((data) => {
console.log(data.results[0]['school.name'])
this.setState({
schoolName: data.results[0]['school.name'],
schoolData: data.results
})
});
In your render method change this line because schoolName is your state variable and not school.
<p>School: {this.state.school} </p>
to
<p>School: {this.state.schoolName} </p>