React newbie here... I am pulling my hair out trying to figure this out... I am trying to use bandsintown API to search for bands then display the results. I am having a difficult time taking the entered band name and then using that as part of the fetch to the bansintown API. It appears that things are re-rendering or not rendering in the correct order, etc. I have tried putting my call to fetch in componentDidMount but that renders before the 'band' state is set...please help... here is a snippet of my code:
import React, { Component } from "react";
class BandSearch extends Component {
constructor(props) {
super(props);
this.state = {
band: "",
events: []
};
}
componentDidMount() {
this.handleChange();
}
async getBand(e) {
e.preventDefault();
try {
const res = await fetch(
`https://rest.bandsintown.com/artists/${
this.state.band
}/events?app_id=acdb6da27e696632f85c3733dd43db52`
);
const events = await res.json();
this.setState({
events: events
});
} catch (e) {
console.log(e);
}
}
handleChange = () => {
this.setState({
band: this.result.value
});
console.log("state", this.state);
};
render() {
console.log(this.state.band);
return (
<div>
<h3>Enter Band to check on tour dates</h3>
<form>
<input
type="text"
placeholder="Enter band name"
ref={input => (this.result = input)}
onChange={this.handleChange}
/>
<button onClick={this.getBand}>Search</button>
</form>
</div>
);
}
}
export default BandSearch;
Thanks so much in advance!!
You need to pass the event from the onChange event of the input like so:
handleChange = (e) => {
this.setState({
band: e.currentTarget.value
});
// console.log("state", this.state); This will have unexpected results. Try to investigate why.
};
Also remove the call to handleChange in componentDidMount
You are actually missing this binding your function in your constructor.
constructor(props) {
super(props);
this.getBand = this.getBand.bind(this);
this.state = {
band: "",
events: []
};
}
Here is the demo of that: https://stackblitz.com/edit/react-hfkqaw?file=index.js
This is from react
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
full reading here: https://reactjs.org/docs/handling-events.html
Related
I have two components, one of which is used for filling out a form, the other is for displaying the input once it is submitted. However, it currently only displays the input until the form is submitted, and then it goes away. What is happening to the state of the parent component when the form is submitted?
class Form extends Component {
constructor(props) {
super(props);
this.state = {
equation: null
};
}
render() {
return (
<div>
<form onSubmit={this.mySubmitHandler}>
<input
type="text"
name="equation"
onChange={this.handleInputChange}
/>
</form>
<Parser value={this.state.equation}/>
</div>
);
}
handleInputChange = event => {
event.preventDefault();
this.setState({
[event.target.name]: event.target.value
});
};
mySubmitHandler = event => {
event.preventDefault();
this.setState({ equation: event.target.value });
alert("You are submitting " + this.state.equation);
console.log(this.state.equation);
};
}
class Parser extends Component {
render() {
return <div>{this.props.value}</div>;
}
The problem here is the event.target.value from the form submit event.
Since the event is coming from form submit, the target element is form and there is no value in the target element.
Update the component like below will solve your problem.
import React, {Component } from 'react';
export default class Hello extends Component {
constructor(props) {
super(props);
this.state = {
equation: null
};
}
render() {
return (
<div>
<form onSubmit={this.mySubmitHandler}>
<input
type="text"
name="equation"
onChange={this.handleInputChange}
/>
<button type="submit">Submit</button>
</form>
<Parser value={this.state.equation}/>
</div>
);
}
handleInputChange = event => {
event.preventDefault();
this.setState({
[event.target.name]: event.target.value
});
};
mySubmitHandler = event => {
event.preventDefault();
alert("You are submitting " + this.state.equation);
console.log(this.state.equation);
};
}
class Parser extends Component {
render() {
return <div>{this.props.value}</div>;
}
}
Check the stackblitz solution.Stackblitz
In mySubmitHandler, event.target.value is undefined, which is why the Parser text is disappearing. If you need to use equation in the submit handler, just use this.state.equation because it is has already been set via handleInputChange
mySubmitHandler = event => {
event.preventDefault();
// event.target.value is undefined
// this.state.equation has already been set via this.handleInputChange
this.setState({ equation: event.target.value });
alert("You are submitting " + this.state.equation);
console.log(this.state.equation);
};
You should not be using this.setState({equation: event.target.value}); in mySubmitHandler.
The event.target for submit is the form itself and it has no value.
Therefore it sets equation to undefined.
The reason you see the correct state when you console.log() it is because setState is async and the state in that function call still has the old value with it.
Remove it and see if it works.
You need to maintain two states equation and inputequation.
Now when you change input setstate inputequation. When you submit setstate equation to inputequation.
And one more thing
<input value={this.state.inputequation}/>
input should be controlled via your state.
Here, I modified the handleInputChange and mySubmitHandler you use this
handleInputChange = event => {
event.preventDefault();
this.setState({
equation: event.target.value
});
};
mySubmitHandler = event => {
event.preventDefault();
alert("You are submitting " + this.state.equation);
console.log(this.state.equation);
};
You should check this stackblitz solution
I have used your code.
import React, { Component } from 'react';
class Form extends Component {
constructor(props) {
super(props);
this.state = {
equation: null
};
}
render() {
return (
<div>
<form onSubmit={this.mySubmitHandler}>
<input
type="text"
name="equation"
onChange={this.handleInputChange}
/>
</form>
<Parser value={this.state.equation}/>
</div>
);
}
handleInputChange = event => {
this.setState({
[event.target.name]: event.target.value
});
};
mySubmitHandler = event => {
event.preventDefault();
alert("You are submitting " + this.state.equation);
};
}
class Parser extends Component {
render() {
return <div>{this.props.value}</div>;
}
}
Simple fix. You are accessing the incorrect property of the form.
this.setState({ equation: event.target.value });
This needs to be the name of the form element which is equation:
this.setState({ equation: event.target.equation.value });
Your updated handler:
mySubmitHandler = event => {
event.preventDefault();
this.setState({ equation: event.target.equation.value });
};
JSFiddle
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 have simple interactive app, and i want to render the CardList component, base on user search. the problem is i want to setTimeOut for the user search, and execute the function after 2000ms from when the user stoped typing.
here is the code, as you can see I managed to get it done, but its hacky and not really useful,im positive there is a better way to do this.
what I'm doing right now is to always change this.state.robots arrry, acording to the user input. notice the searchBox component has an input field
class App extends Component {
constructor(){
super();
this.state = {
robots: robots,
searchField: ''
}
}
onSearchange = (event) =>{
let timeOut = null;
this.setState({searchField: event.target.value,robots:robots});
event.target.onkeyup = (e) =>{
clearTimeout(timeOut);
timeOut = setTimeout(()=> {
const filterRobots = this.state.robots.filter(robot => {
return robot.name.toLowerCase().includes(this.state.searchField.toLowerCase());
})
this.setState({robots: filterRobots});
},2000);
}
}
render() {
return (
<div className = "tc">
<h1>RoboFriend</h1>
<SearchBox searchChange = {this.onSearchange}/>
<CardList robots = {this.state.robots} />
</div>
);
}
}
I would like to be able to send fillterRobots array dynamically to the CardList component so i can render the results properly
I would use something like lodash's debounce(). You don't just want a delay, you also want to cancel the previous delayed function execution if you receive a new event before the current delayed function has executed.
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.delayedCallback = _.debounce(this.handleChange, 1000);
}
handleChange(value) {
this.setState({ value });
}
onChange(event) {
event.persist();
this.delayedCallback(event.target.value);
}
render() {
return (
<div>
<input onChange={this.onChange.bind(this)} />
<p>{this.state.value}</p>
</div>
);
}
}
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 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 :)