Not able to push item into List in React Js - javascript

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!

Related

How to re-render a list after deleting an element

I am trying to write a delete method in order to delete an element from a list, first of all I am not being able to write it in a setState function so I have it as a direct function call, How can I manage to signal a re-render after the direct function or manage to place the function in the setState method for automatic re-render?
class TASKMANAGER extends Component {
constructor(props){
super(props);
this.state= {
name: "",
description:"",
priority: "urgent",
tasklist: [],
}
this.handleTitleChange= this.handleTitleChange.bind(this);
//this.handleDescriptionChange= this.handleDescriptionChange.bind(this);
//this.handlePriorityChange= this.handleDescriptionChange.bind(this);
this.handleClick= this.handleClick.bind(this);
}
handleTitleChange = event => {
this.setState( {
name: event.target.value
})
};
handleDescriptionChange = event => {
this.setState({
description: event.target.value
})
};
handlePriorityChange = event => {
this.setState({
priority: event.target.value
})
};
handleClick = event => {
this.setState((state) => {
const tasklist = [
...state.tasklist,
[
state.name,
state.description,
state.priority
]
];
return {
tasklist
};
});
//console.log(this.state.tasklist);
};
handleDelete = index => {
this.setState(() => {
this.state.tasklist.splice(index, 1)
});
console.log(this.state.tasklist)
} THIS ONE IS THE FUNCTION I CANNOT SET TO WORK TO TRIGGER THE AUTO RE-RENDER
render() {
const task_item = this.state.tasklist.map((arr, index) => (
<li
key= {index}
className= 'task'>
Task: {arr[0]} <br />
Description: {arr[1]} <br />
Priority: {arr[2]} <br />
<div className='delete-button' onClick={
/*() => {this.state.tasklist.splice(index, 1);}*/ THIS ONE IS THE DIRECT FUNCTION THAT WORKS, BUT DOESN'T TRIGGER THE RE-RENDER, IT SHOWS WHEN I TYPE AGAIN ON THE INPUTS
this.handleDelete
}>delete</div>
</li>
))
return (
<div>
<div className= 'task-form'>
<form>
<div>
<label>Name your task!</label>
<input type= 'text' id='task-title' value={this.state.name} onChange={this.handleTitleChange} />
</div>
<div>
<label>Description?</label>
<textarea id='description' value={this.state.description} onChange={this.handleDescriptionChange}/>
</div>
<div>
<label>Priority?</label>
<select value={this.state.priority} onChange={this.handlePriorityChange}>
<option value='urgent'>Urgent</option>
<option value='regular'>Regular</option>
<option value='Can wait'>Can wait</option>
</select>
</div>
</form>
<button onClick={this.handleClick}>PRESS</button>
</div>
<div className='list-items'>
<ul className='list-render'>
{task_item}
</ul>
</div>
</div>
)
}}
export default TASKMANAGER
You shouldn't be making any mutations to the current state, but instead build a new state from the existing state, generating a new, filtered array along the way
handleDelete = index => {
this.setState((state) => ({
...state,
tasklist: state.taskList.filter((_,i) => i != index)
}));
}
When you map your taskList to JSX below, you will need to avoid using the index of the item as key, because the optimizations react makes using the key value will be operating under broken assumptions. Use a key value that remains constant and unique per item. Perhaps its name, or an identifier that is attached to it when created.
There is no need to assign the list. Just splice it. Use something like this to change the state:
delete={()=>{this.setState({phase:1-this.state.phase});
this.state.list.splice(index,1)}}

React error Maximum update depth exceeded

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;

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);
}

How to get values from multiple Textareas in React

I have a form that has more than 1 textarea(3 to exact). The issue is that, if I type something in 1 textarea then the other textareas are also filled and updated. And I dont' want that. I'm using an onChange handler to make them controlled components but they're not behaving as expected. I've looked online but couldn't find a solution! How do I make them update individually on change/type?
React Textarea component
const Textarea = ({ labelText, placeholder, value, name, onChange }) => {
return (
<div className="textarea-panel">
<label>{labelText}</label>
<textarea
style={{ display: 'block' }}
cols="60"
rows="5"
placeholder={placeholder}
value={value}
name={name}
onChange={onChange}
/>
</div>
);
}
export default Textarea;
React Form component
import Textbox from './Textbox';
export class Form extends Component {
constructor(props) {
super(props);
this.state = {
data: {
career: '',
experience: '',
additionalInformation: '',
},
};
this.handleTextareaChange = this.handleTextareaChange.bind(this);
}
handleTextareaChange(event) {
this.setState({
data: {
[event.target.name]: event.target.value,
},
});
}
render() {
return (
<form>
<ul>
<li>
<Textbox
labelText="Write your career "
value={this.state.data.career}
placeholder="e.g. extra information"
name="career"
onChange={this.handleTextareaChange}
/>
<span>{this.state.data.career}</span>
</li>
<li>
<Textbox
labelText="Write your experience"
value={this.state.data.experience}
placeholder="e.g. extra information"
name="experience"
onChange={this.handleTextareaChange}
/>
<span>{this.state.data.experience}</span>
</li>
<li>
<Textbox
labelText="Additional information"
value={this.state.data.additionalInformation}
placeholder="e.g. extra information"
name="additionalInformation"
onChange={this.handleTextareaChange}
/>
<span>{this.state.data.additionalInformation}</span>
</li>
</ul>
</form>
);
}
}
export default Form;
You're overriding the entire state with a new key on the data object. Every time the onChange handler is called the entire state gets overriden with a new data object with the current textbox's name as the key. Instead just update the particluar key on the data like this
Try it here : Codesandbox
handleTextareaChange(event) {
const { data } = this.state
data[event.target.name] = event.target.value
this.setState({
data
});
}
Hope this helps !

The '.onChange' method doesn't get the newest info of 'Input' in React.js

import reqwest from './public/reqwest.js'
const PAGE_SIZE = 10
class App extends Component {
constructor() {
super()
this.state = {
page: 1,
arr: []
}
}
singleInfo(page) {
reqwest({
url: 'https://cnodejs.org/api/v1/topics',
data: {
limit: PAGE_SIZE,
page: this.state.page
},
success: function (data) {
this.setState({
arr: data.data
})
}.bind(this)
})
}
changeState(newState) {
this.setState(newState)
this.singleInfo(newState.page)
}
render() {
return (
<div>
<Menu val={this.state.page} changeParentState={(state) => this.changeState(state)} />
<List arr={this.state.arr} />
</div>
);
}
}
class Menu extends Component {
handleChange(event) {
if(event.target.value) {
this.props.changeParentState({
page: event.target.value
})
}
}
render() {
console.log(this)
return <input type="text" defaultValue={this.props.val} onChange={(event) => this.handleChange(event)} />
}
}
class List extends Component {
render() {
return <ul>
{
this.props.arr.map((ele) => {
return (
<li key={ ele.id }>
<p className="title">{ ele.title }</p>
<p className="date">{ ele.create_at }</p>
<p className="author">{ ele.author.loginname }</p>
</li>
)
})
}
</ul>
}
}
I can't get the current value of the input by onChange in Menu module.
In my code, the App has two child components - List & Menu.
You can input the page in Menu component, so it will send Ajax() to get the info of the page. But the truth is: After I change the value of input like 1 -> 10, the ajax get the value of 1.
Before this, I know the difference between keyup or keydown and keypress. They have the difference cross browser. But I just want get the current value of the input By React.js.
First, change:
<input type="text" defaultValue={this.props.val} onChange={(event) => this.handleChange(event)} />
To:
<input type="text" value={this.props.val} onChange={(event) => this.handleChange(event)} />
so that your input will update to the correct value on re-render.
Second, remember that setState is often asynchronous. So do not expect the state to be changed right after calling setState.
Your changeState method is good in this respect since it passes newState to singlePageRequest. However singlePageRequest does not use the supplied value and instead uses this.state.page. Change it to use the supplied value and you should be OK:
singleInfo(page) {
reqwest({
url: 'https://cnodejs.org/api/v1/topics',
data: {
limit: PAGE_SIZE,
page: page
},
success: function (data) {
this.setState({
arr: data.data
})
}.bind(this)
})
}

Categories

Resources