React error Maximum update depth exceeded - javascript

I'm doing my first few experiments with React and in this component I am calling an external API to get a list of all NBA players, filter them by the teamId which was received as a component's prop and finally render the markup of the filtered players.
One consideration is that since I call the API and get a large list I keep it in the component's state so that new calls to this component would use that state instead of calling the API again. This is not production code and I don't own the API so I do this because I was getting a "Too many requests" message since I am continously trying stuff.
Anyway, when I try this code I get the already famous:
Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
I've looked into the markup and I don't think I am making any method calls that would cause the render method to fire again and so forth, so I am at a loss as to why this is happening.
Thank you in advance for any help.
Here's the code in question:
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
state = {
players: null,
selectedTeamPlayers: null
};
render() {
if (this.props.teamId === null) return null;
if (this.state.players !== null) {
var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
var markup = this.getMarkup(selectedTeamPlayers);
this.setState({selectedTeamPlayers: markup});
} else {
this.getPlayersList();
}
return (
this.state.selectedTeamPlayers
);
}
getPlayersList() {
let api = new ExternalApi();
let that = this;
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
that.setState({players: players});
var selectedTeamPlayers = that.filterPlayersByTeamId(players);
var markup = that.getMarkup(selectedTeamPlayers);
that.setState({selectedTeamPlayers: markup});
});
});
}
filterPlayersByTeamId(players) {
return players.filter(p => {
return p.teamId === this.props.teamId;
});
}
getMarkup(players) {
var result = players.map(p => {
<li key={p.id}>
<div>
<label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
</div>
</li>
});
return (
<ul>
{result}
</ul>
);
}
}
export default Players;

#Sergio Romero - You CAN NOT set state in a render function, as that set state will call a new render, which will set state again and call a new render, and generates an infinite loop. Your loading of the data is in the render and setting state, which is also creating infinite loops. You need to completely re-write your render to only be a view of state and props (it should never manipulate or load data). I think what you want, is more like this:
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
static propTypes = {
teamId: PropTypes.number
};
static defaultProps = {
teamId: null
};
constructor(props) {
super(props);
this.state = {
players: null
};
}
componentDidMount() {
this.getPlayerList();
}
filterPlayersByTeamId(players, teamId) {
return players.filter(p => {
return p.teamId === teamId;
});
}
getPlayersList = () => {
let api = new ExternalApi();
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
this.setState({players});
});
});
};
render() {
if (!this.props.teamId || !this.state.players) return null;
const selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players, this.props.teamId);
return (
<ul>
{
selectedTeamPlayers.map(player => {
<li key={player.id}>
<div>
<label htmlFor="firstName">First Name</label><input type="text" name="firstName" value={player.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label><input type="text" name="lastName" value={player.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label><input type="text" name="position" value={player.position} readOnly></input>
</div>
</li>
})
}
</ul>
);
}
}
export default Players;

if State and Props change the Component Will re-render.
in your render() function:
if (this.state.players !== null) {
var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
var markup = this.getMarkup(selectedTeamPlayers);
// this.setState({selectedTeamPlayers:
}
Try changing commented line, every time players not null the Component state is updated therefore the component Render function will run again.

As of the fact that we can not set state inside render function because it will cause side effect, you cannot call the getPlayersList() inside of the render function.
The solution mentioned by #Jason Bellomy is the proper way to solve this with calling getPlayerList inside of the componentDidMount, because it invoked immediately after a component is inserted into the tree, thus it's a place to set initial data for rendering a page.
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
state = {
players: null,
selectedTeamPlayers: null,
};
componentDidMount(){
this.getPlayersList();
}
render() {
if (this.props.teamId === null) return null;
if (this.state.players !== null && this.state.selectedTeamPlayers !== null) {
return this.getMarkup(selectedTeamPlayers);
} else {
return (<span> Loading ... </span>);
}
}
getPlayersList() {
let api = new ExternalApi();
let that = this;
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
var selectedTeamPlayers = that.filterPlayersByTeamId(players);
that.setState({
players: players,
selectedTeamPlayers: selectedTeamPlayers,
});
});
});
}
filterPlayersByTeamId(players) {
return players.filter(p => {
return p.teamId === this.props.teamId;
});
}
getMarkup(players) {
var result = players.map(p => {
<li key={p.id}>
<div>
<label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
</div>
</li>
});
return (
<ul>
{result}
</ul>
);
}
}
export default Players;

Related

Not able to push item into List in React Js

I want to push the tip which I'm calculating in #this.calculate onto tip_arr[]. In that I'm not able to access my first item of as it is showing ["",item,item]. Here my first item is getting empty just the string. Here I'm calculating per person tip how much he is paying and after that diplay that.
export default class Calculation extends React.Component {
constructor(props) {
super(props)
this.state = {
amount: '',
tip_per: '',
name: '',
tip: '',
name_arr:[],
tip_arr:[]
}
this.handle = (event) => {
this.setState({ tip_per: event.target.value });
}
this.calculate = () => {
this.setState({ tip: ((this.state.amount) * this.state.tip_per) / 100 })
this.name_change()
this.tip_change()
}
this.name_change=()=>{
let{name_arr,name}=this.state
name_arr.push(name)
}
this.tip_change=()=>{
let{tip_arr,tip}=this.state
tip_arr.push(tip)
}
}
render(){
return(
<>
<div className='Header'>
<header>Tip Calculator<br />Build in React</header>
</div>
<div className='Input'>
<h4>Enter the Bill Amount:-</h4>
<input type='number' className='width' value={this.state.amount}
onChange={(e) => this.setState({ amount: e.target.value })}></input>
<hr />
<div className='inner'>
<p>How was the Service:- <span>
<select onChange={this.handle}>
<option>Choose...</option>
<option value={20}>Excellent:- 20%</option>
<option value={10}>Moderate:- 10%</option>
<option value={5}>Bad:- 5%</option>
</select>
<input type='text' className='filed' placeholder="Customer Name"
value={this.state.name} onChange={(e) => this.setState({ name: e.target.value })} />
<button type='button' className='filed else' onClick={this.calculate}>Add Customer</button>
</span></p>
</div>
</div>
<div className='Output'>
<p>Customer List:-</p><hr />
<ul className='UL'>
{this.state.name_arr.map((item,index)=>{
return <li key={index}>{item} offering a tip of</li>})}
<span> <ul className='UL'>
{this.state.tip_arr.map((item,index)=>{
return <li key={index}>{item} rupees </li>})}
</ul>
</span>
</ul>
{console.log(this.state.name_arr)}
{console.log(this.state.tip_arr)}
</div>
</>
)
}
};
You're attempting to mutate a state variable. You have to update the value via setState. You should also pass setState a function instead of a plain object in this case so that you can access the correct value since state updates are asynchronous.
tip_change = () => {
this.setState((prevState) => {
const { tip, tip_arr } = prevState;
return {
tip_arr: [...tip_arr, tip]
};
});
}
You can't change state directly.
Do Not Modify State Directly.
Read how to use state correctly from React documentation
To update it, you need to call this.setState eg:
this.tip_change=()=>{
let{tip_arr,tip}=this.state
this.setState(prevState => {
let newTipArray = [prevState.tip_arr, tip]
return {tip_arr: newTipArray }
})
}
This should also be the case for name_arr state. eg:
this.name_change=()=>{
let{name_arr,name}=this.state
//name_arr.push(name) // can't manipulate state directly.
this.setState(prevState => {
let newNameArr = [prevState.name_arr, name]
return {name_arr: newTipArray }
})
}
Update: as #Phishy, using second form of setState() that accepts a function rather than an object helps fix some of state quirks because of the asyncronous nature of React state.
Here is a nice article that explains why to Beware: React setState is asynchronous!

Conditional rendering on select

I am pretty new to the wonderful world of React.
I have two inputs passing data through from an API that renders a list of options. And I want to send the selected inputs from those options back to the parent in the input fields to display for another search.
I have tried passing state down to them and render them them optionally with both a ternary and an if else statement in the "SearchCityList" component in several ways but I either get both lists rendered and they would have to choose between one list that is doubled to put in each input field or it only puts the selected value in one input. Would appreciate any & all suggestions Thanks!
class Form extends Component {
state = {
showComponent: false,
showComponent2: false,
};
// open/close control over SearchCity component box
openSearch = () => {
this.setState({ showComponent: true });
};
openSearch2 = () => {
this.setState({ showComponent2: true });
};
closeSearch = () => {
this.setState({
showComponent: false,
showComponent2: false
});
};
// Passed down cb function to get selected city search in selectCity component
GoingTo = (flights) => {
this.setState({ GoingTo: [flights] });
};
LeavingFrom = (flights) => {
this.setState({ LeavingFrom: [flights] });
};
render() {
return (
<div>
<form className="form-fields container">
<div className="inputs">
<h1>Search for a flight!</h1>
<div className="depart">
<input
onClick={this.openSearch}
className="flight-search"
placeholder="Leaving From"
value={this.state.LeavingFrom}
></input>
<input type="date"></input>
</div>
<div className="Returning">
<input
onClick={this.openSearch2}
className="flight-search"
placeholder="Going To "
value={this.state.GoingTo}
></input>
<input type="date" placeholder="Returning"></input>
</div>
</div>
<button>Check Flights!</button>
</form>
{this.state.showComponent || this.state.showComponent2 ? (
<SearchCity
openSearch={this.openSearch}
openSearch2={this.openSearch2}
flightSearch={this.state.flightSearch}
closeSearch={this.closeSearch}
GoingTo={this.GoingTo}
LeavingFrom={this.LeavingFrom}
onSearchSubmission={this.onSearchSubmission}
closeSearch={this.closeSearch}
/>
) : null}
</div>
);
}
}
export default Form;
class SearchCity extends Component {
state = {
LeavingFrom: "",
GoingTo: "",
search: "",
flightSearch: [],
};
// Search submission / api call
onSearchSubmission = async (search) => {
const response = await Axios.get(
{
headers: {
"
useQueryString: true,
},
}
);
// set New state with array of searched flight data sent to searchCity component
const flightSearch = this.setState({ flightSearch: response.data.Places });
};
// Callback function to send search/input to parent "Form" component
submitSearch = (e) => {
e.preventDefault();
this.onSearchSubmission(this.state.search);
};
// closeSearch callback function sent from Form component to close pop up search box when X is pressed
closeSearch = () => {
this.props.closeSearch();
};
render() {
return (
<div className="container search-list">
<form onChange={this.submitSearch}>
<i className="fas fa-times close-btn" onClick={this.closeSearch}></i>
<input
onChange={(e) => this.setState({ search: e.target.value })} //query-search api
value={this.state.search}
className="search-input"
type="text"
placeholder="Search Locations"
></input>
<div className="search-scroll">
<SearchCityList
openSearch={this.props.openSearch}
openSearch2={this.props.openSearch2}
LeavingFrom={this.props.LeavingFrom}
GoingTo={this.props.GoingTo}
flightSearch={this.state.flightSearch}
/>
</div>
</form>
</div>
);
}
}
export default SearchCity;
function SearchCityList({ flightSearch, LeavingFrom, GoingTo }) {
const renderList = flightSearch.map((flights) => {
return (
<div>
<SelectCityLeaving LeavingFrom={LeavingFrom} flights={flights} />
<SelectCityGoing GoingTo={GoingTo} flights={flights} />
</div>
);
});
return <div>{renderList}</div>;
}
export default SearchCityList;
First of all, when dealing with state, make sure you initialize in the constructor and also ensure you bind your handlers to this component instance as this will refer to something else in the handlers if you don't and you won't be able to call this.setState().
constructor(props) {
super(props); // important
state = {
// your state
};
// make sure to bind the handlers so `this` refers to the
// component like so
this.openSearch = this.openSearch.bind(this);
}

Value of this is undefined

I have a this value inside an if statement, nested inside a my handleFormChange function. I've tried to use arrow functions with this function to bind the value of this but im getting the following error message:
TypeError: Cannot set property 'author' of undefined
From my understanding usually you find the this value by looking at where the function containing this is called. However, in my case im struggling to work this out. Can anyone explain to me why it is undefined and how to solve this issue? Here is the code:
class CommentForm extends React.Component{
constructor(props){
super(props)
var comment={author:'', message:''}
}
handleSubmit= (e)=>{
e.preventDefault()
var authorVal = this.comment.author;
var textVal = this.comment.message;
//this stops any comment submittal if anything missing
if (!textVal || !authorVal) {
return;
}
this.props.onCommentSubmit(this.comment);
//reset form values
e.target[0].value = '';
e.target[1].value = '';
return;
}
handleFormChange= (e)=>{
e.preventDefault()
if(e.target.name==='author'){
var author = e.target.value.trim();
this.comment.author = author
}else if(e.target.name==='message'){
var message = e.target.value.trim();
this.comment.message = message
}
}
render() {
return (
<form className = "ui form" method="post" onChange={(e)=>{this.handleFormChange(e)}} onSubmit={(e)=>{this.handleSubmit(e)}}>
<div className="form-group">
<input
className="form-control"
placeholder="user..."
name="author"
type="text"
/>
</div>
<div className="form-group">
<textarea
className="form-control"
placeholder="comment..."
name="message"
/>
</div>
<div className="form-group">
<button disabled={null} className="btn btn-primary">
Comment ➤
</button>
</div>
</form>
);
}
}
export default CommentForm
The first step into learning how to do what you want is to study how React's State works (official docs are great at explaning it).
This example is not complete, but should guide you through the proccess.
class CommentForm extends Component {
constructor(props) {
super(props);
this.state = {
author : '',
message : '',
}
this.onChangeAuthorName = this.onChangeAuthorName.bind(this);
this.onBlurAuthorName = this.onBlurAuthorName.bind(this);
}
onChangeAuthorName(e) {
this.setState({ author: e.target.value });
}
onBlurAuthorName() {
// trim on blur (or when you send to the network, to avoid
// having the user not being able to add empty whitespaces
// while typing
this.setState({ author: this.state.author.trim() })
}
render() {
return (
...
<input value={this.state.author} onChange={this.onChangeAuthorName} onBlur={this.onBlurAuthorName} />
...
);
}
}
Usually, when you want to "set" variables in React, you don't add them as you do to in Javascript classes (this.comment = e.target.value), but instead, use the function setState(). From the docs:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
(NOTE: Alternatively, this could be done using React Hooks, but I recommend you learn the lifecycle methods firsthand. Good luck!)
I decided to write even if you proposed the correct answer for the simple reason that I think my code is closer to what it published.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
constructor(props) {
super(props);
this.state = {
comment: {},
some: 1
};
}
handleFormChange = e => {
e.preventDefault();
let { comment } = this.state;
const newCommentState = function() {
let returnObj = { ...comment };
returnObj[this.target.name] = this.target.value.trim();
return returnObj;
}.bind(e)();
this.setState({ comment: newCommentState });
};
handleSubmit = e => {
e.preventDefault();
let { comment } = this.state;
if (!comment.author || !comment.message) return;
this.props.onCommentSubmit(comment);
this.setState({ comment: {} });
e.target[0].value = "";
e.target[1].value = "";
};
render() {
return (
<div>
<form
className="ui form"
method="post"
onChange={e => {
this.handleFormChange(e);
}}
onSubmit={e => {
this.handleSubmit(e);
}}
>
<div className="form-group">
<input
className="form-control"
placeholder="user..."
name="author"
type="text"
/>
</div>
<div className="form-group">
<textarea
className="form-control"
placeholder="comment..."
name="message"
/>
</div>
<div className="form-group">
<button disabled={null} className="btn btn-primary">
Comment ➤
</button>
</div>
</form>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example:

Getting value from react component

I have a component InputArea with state = {input: ''}
Then I map several of these components in a container and write them in state = {inputAreas: []}
Now, how can I get inputs in the container? Logging this.state.inputAreas[0] returns object like this:
{$$typeof: Symbol(react.element), type: ƒ, key: "1", ref: null, props:
{…}, …}
In elements it shows like this:
<input type="text" class="form-control" name="input" value="abc">
Using this.state.prefooterArea[0].value gives undefined.
I also tried passing input from component to container as props, but it says getInput is not a function. From what I understood it has something to do with the fact I used map in the container. I can't use redux in this project.
Code of component
class PrefooterAreaInput extends Component {
state = {
input: ''
}
textChangedHandler = (event) => {
let newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState);
}
render() {
return (
<div>
<input
className="form-control"
type="text"
name="input"
value = {this.state.input}
onChange={this.textChangedHandler}
/>
</div>
)
}
}
Code of container
class DescriptionFrame extends Component {
state = {,
prefooterArea: [<PrefooterAreaInput key={1}/>]
};
addFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length < prefooterInputFieldsMax) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.push(
<PrefooterAreaInput key={this.state.prefooterArea.length + 1} />
);
this.setState({ prefooterArea: newPrefooterArea });
}
};
removeFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length > 1) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.splice(newPrefooterArea.length - 1);
this.setState({ prefooterArea: newPrefooterArea });
}
render() {
// want to get this.state.prefooterArea[0]'s value
return (
<div>
{this.state.prefooterArea}
<a
className="nav-link"
href=""
onClick={this.addFooterInputHandler}
>
Add More
</a>
<a
className="nav-link"
href=""
onClick={this.removeFooterInputHandler}
>
Remove Last
</a>
</div>
);
}
}
Figured it out. This caused problem.
prefooterArea: [<PrefooterAreaInput key={1}/>]
I should have added that initial PrefooterAreaInput with lifecycle method instead. With that I was able to pass state just fine.
Are you trying to achieve something like this ?
child component :
export default class InputBox extends React.Component {
render() {
return (
<input onChange={event => this.props.onChange(event.target.value)} />
);
}}
parent component :
import InputBox from './InputBox';
class FilterBar extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: "" //get input value from state this input
};
this.updateFilters = this.updateFilters.bind(this);
}
updateFilters(i) {
this.setState({ inputs: i }); // this will print whatever input you type
}
render() {
return (
<div>
<InputBox onChange={(i) => this.updateFilters(i)} />
</div>
);
}
}

How do I edit multiple input controlled components in React?

I have a component that stores a contact object as state - {firstName: "John", lastName: "Doe", phone: "1234567890} I want to create a form to edit this object but if I want the inputs to hold the value of the original contact parameter, I need to make each input a controlled component. However, I don't know how to create a handleChange function that will adjust to each parameter because my state only holds {contact: {...}}. Below is what I currently have -
getInitialState: function () {
return ({contact: {}});
},
handleChange: function (event) {
this.setState({contact: event.target.value });
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange} value={this.state.contact.lastName}/>
</div>
);
}
I wish in my handleChange I can do something like
handleChange: function (event) {
this.setState({contact.firstName: event.target.value });
}
There's a "simple" way to do this, and a "smart" way. If you ask me, doing things the smart way is not always the best, because I may be harder to work with later. In this case, both are quite understandable.
Side note: One thing I'd ask you to think about, is do you need to update the contact object, or could you just keep firstName etc. directly on state? Maybe you have a lot of data in the state of the component? If that is the case, it's probably a good idea to separate it into smaller components with narrower responsibilities.
The "simple" way
changeFirstName: function (event) {
const contact = this.state.contact;
contact.firstName = event.target.value;
this.setState({ contact: contact });
},
changeLastName: function (event) {
const contact = this.state.contact;
contact.lastName = event.target.value;
this.setState({ contact: contact });
},
changePhone: function (event) {
const contact = this.state.contact;
contact.phone = event.target.value;
this.setState({ contact: contact });
},
render: function () {
return (
<div>
<input type="text" onChange={this.changeFirstName.bind(this)} value={this.state.contact.firstName}/>
<input type="text" onChange={this.changeLastName.bind(this)} value={this.state.contact.lastName}/>
<input type="text" onChange={this.changePhone.bind(this)} value={this.state.contact.phone}/>
</div>
);
}
The "smart" way
handleChange: function (propertyName, event) {
const contact = this.state.contact;
contact[propertyName] = event.target.value;
this.setState({ contact: contact });
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange.bind(this, 'firstName')} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange.bind(this, 'lastName')} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange.bind(this, 'phone')} value={this.state.contact.lastName}/>
</div>
);
}
Update: Same examples using ES2015+
This section contains the same examples as shown above, but using features from ES2015+.
To support the following features across browsers you need to transpile your code with Babel using e.g.
the presets es2015 and react,
and the plugin stage-0.
Below are updated examples, using object destructuring to get the contact from the state,
spread operator to
create an updated contact object instead of mutating the existing one,
creating components as Classes by
extending React.Component,
and using arrow funtions to
create callbacks so we don't have to bind(this).
The "simple" way, ES2015+
class ContactEdit extends React.Component {
changeFirstName = (event) => {
const { contact } = this.state;
const newContact = {
...contact,
firstName: event.target.value
};
this.setState({ contact: newContact });
}
changeLastName = (event) => {
const { contact } = this.state;
const newContact = {
...contact,
lastName: event.target.value
};
this.setState({ contact: newContact });
}
changePhone = (event) => {
const { contact } = this.state;
const newContact = {
...contact,
phone: event.target.value
};
this.setState({ contact: newContact });
}
render() {
return (
<div>
<input type="text" onChange={this.changeFirstName} value={this.state.contact.firstName}/>
<input type="text" onChange={this.changeLastName} value={this.state.contact.lastName}/>
<input type="text" onChange={this.changePhone} value={this.state.contact.phone}/>
</div>
);
}
}
The "smart" way, ES2015+
Note that handleChangeFor is a curried function:
Calling it with a propertyName creates a callback function which, when called, updates [propertyName] of the
(new) contact object in the state.
class ContactEdit extends React.Component {
handleChangeFor = (propertyName) => (event) => {
const { contact } = this.state;
const newContact = {
...contact,
[propertyName]: event.target.value
};
this.setState({ contact: newContact });
}
render() {
return (
<div>
<input type="text" onChange={this.handleChangeFor('firstName')} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChangeFor('lastName')} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChangeFor('phone')} value={this.state.contact.lastName}/>
</div>
);
}
}
ES6 one liner approach
<input type="text"
value={this.state.username}
onChange={(e) => this.setState({ username: e.target.value })}
id="username"/>
The neatest approach
Here is an approach that I used in my simple application. This is the recommended approach in React and it is really neat and clean. It is very close to ArneHugo's answer and I thank hm too. The idea is a mix of that and react forms site.
We can use name attribute of each form input to get the specific propertyName and update the state based on that. This is my code in ES6 for the above example:
class ContactEdit extends React.Component {
handleChangeFor = (event) => {
const name = event.target.name;
const value = event.target.value;
const { contact } = this.state;
const newContact = {
...contact,
[name]: value
};
this.setState({ contact: newContact });
}
render() {
return (
<div>
<input type="text" name="firstName" onChange={this.handleChangeFor} />
<input type="text" name="lastName" onChange={this.handleChangeFor}/>
<input type="text" name="phone" onChange={this.handleChangeFor}/>
</div>
);
}
}
The differences:
We don't need to assign state as value attribute. No value is needed
The onChange method does not need to have any argument inside the function call as we use name attribute instead
We declare name and value of each input in the begening and use them to set the state properly in the code and we use rackets for name as it is an attribute.
We have less code here and vey smart way to get any kind input from the form because the name attribute will have a unique value for each input.
See a working example I have in CodPen for my experimental blog application in its early stage.
There are two ways to update the state of a nested object:
Use JSON.parse(JSON.stringify(object)) to create a copy of the object, then update the copy and pass it to setState.
Use the immutability helpers in react-addons, which is the recommended way.
You can see how it works in this JS Fiddle. The code is also below:
var Component = React.createClass({
getInitialState: function () {
return ({contact: {firstName: "first", lastName: "last", phone: "1244125"}});
},
handleChange: function (key,event) {
console.log(key,event.target.value);
//way 1
//var updatedContact = JSON.parse(JSON.stringify(this.state.contact));
//updatedContact[key] = event.target.value;
//way 2 (Recommended)
var updatedContact = React.addons.update(this.state.contact, {
[key] : {$set: event.target.value}
});
this.setState({contact: updatedContact});
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange.bind(this,"firstName")} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange.bind(this,"lastName")} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange.bind(this,"phone")} value={this.state.contact.phone}/>
</div>
);
}
});
ReactDOM.render(
<Component />,
document.getElementById('container')
);
Here is generic one;
handleChange = (input) => (event) => {
this.setState({
...this.state,
[input]: event.target.value
});
}
And use like this;
<input handleChange ={this.handleChange("phone")} value={this.state.phone}/>
<input> elements often have a property called name.
We can access this name property from the event object that we receive from an event handler:
Write a generalized change handler
constructor () {
super();
this.state = {
name: '',
age: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange (evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
render () {
return (
<form>
<label>Name</label>
<input type="text" name="name" onChange={this.handleChange} />
<label>Age</label>
<input type="text" name="age" onChange={this.handleChange} />
</form>
);
}
source
updatePrevData=(event)=>{
let eventName=event.target.name;
this.setState({
...this.state,
prev_data:{
...this.state.prev_data,
[eventName]:event.target.value
}
})
console.log(this.state)
}
You can do it without duplicate code and easy way
handleChange=(e)=>{
this.setState({
[e.target.id]:e.target.value
})
}
<Form.Control type="text" defaultValue={this.props.allClients.name} id="clientName" onChange={this.handleChange}></Form.Control>
<Form.Control type="email" defaultValue={this.props.allClients.email} id="clientEmail" onChange={this.handleChange}></Form.Control>
handleChange(event){
this.setState({[event.target.name]:event.target.value});
this.setState({[event.target.name]:event.target.value});
}

Categories

Resources