I'm trying use a input to update the number of items of a list. Right now my code is updating the input value correctly but the array that i'm using to generate the list is updating only if I call the OnBlur event twice. I don't know where I'm failing. I used the OnChange event and the problem is the same.
The function that handles the update logic is UpdateList
Heres the jsBin working http://jsbin.com/favabamitu/edit?html,js,console,output
Here's my code:
var InputData = React.createClass({
getInitialState: function() {
return({
number_of_locations: 4,
thickness_m: []
});
},
componentDidMount: function() {
for (var i = 0; i < this.state.number_of_locations; i++) {
this.state.thickness_m.push(0);
console.log("state has been intitialized");
}
},
UpdateList: function(event) {
var value = event.target.value;
var locations = parseInt(this.state.number_of_locations);
console.log("local locations value is "+ locations);
var thickness = this.state.thickness_m;
if (locations > thickness.length) {
var dif = locations - thickness.length;
for (var i = 0; i < dif; i++) {
thickness.push(0);
}
console.log('up with' + dif + 'dif');
} else if (locations < thickness.length) {
var dif = thickness.length - locations;
thickness.splice(0, dif);
console.log('down with' + dif + 'dif');
}
this.setState({
number_of_locations: value,
thickness_m: thickness
});
},
Lister: function(number, index) {
return (
<li key = {index}> {number}</li>
);
},
render: function() {
var thickness_values = this.state.thickness_m
return (
<div className="row">
<div className="col-md-6">
<div className="component">
<div className="form-group">
<label htmlFor="read-method">Reading Method</label>
<select className="form-control" name="read-method" id="read-method" required>
<option value="--">--</option>
<option value="1">Point Thickness Readings - PTR</option>
<option value="2">Critical Thickness Profiles - CTP</option>
</select>
</div>
<div className="form-group">
<label htmlFor="number-of-locations">Number of Locations</label>
<input onBlur={this.UpdateList}
defaultValue={this.state.number_of_locations}
className="form-control"
type="number"
max="50"
min="1"
id="number-of-locations"/>
</div>
<div className="form-group">
<label htmlFor="separation">Separation</label>
<input className="form-control" type="number" id="separation"/>
</div>
<div className="form-group">
<ul>
{thickness_values.map( this.Lister )}
</ul>
<small>your list now has {this.state.number_of_locations} items</small>
</div>
</div>
</div>
<div className="col-md-6">
hello
</div>
</div>
)
}
})
ReactDOM.render( <InputData /> ,
document.getElementById('input-measurement-data')
);
<div id="input-measurement-data">
<!-- This element's contents will be replaced with your component. -->
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Any help would be greatly appreciated!
Change this line:
var locations = parseInt(this.state.number_of_locations);
to
var locations = parseInt(value);
For your code it is updated in next onChange, because you are updating this.state.number_of_locations at the end of function UpdateList but calculating number of locations before this update.
BTW, updating this state before locations calculation wouldn't help, because of this React feature:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
Related
I realize that setTotal(newState) is causing an infinite loop because when it gets updated, it re-renders > causes the function 'showDiscounted' to be called again > state gets updated > and it just goes on forever.
I know that I have to use useEffect to stop this issue, but I'm just not quite sure how I can implement it. I've thought about using useRef because it doesn't cause rerender, but I need it to rerender to show the state 'total' somewhere else. What is the best way to go about this?
import { useEffect, useState } from "react";
const QuoteCalcBot = ({ parts, setParts, option, discount }) => {
const [total, setTotal] = useState([0,0,0]);
const [discountedTotal, setdiscountedTotal] = useState([0,0,0]);
//change individual discount for a part
const handleDiscountChange = (part, e) => {
console.log(e.target.value);
console.log(part);
}
//takes in the full price then shows the discounted price of a part. Also adds up the discounted price to discountedTotal for displaying it later
const showDiscounted = (price) => {
const temp = Math.ceil((price * ((100 - discount) / 100)) / 36)
const newState = discountedTotal.map((t, i) => i === option? t + temp : t)
console.log(newState);
setTotal(newState); //this causes infinte loop ERROR
return (
<div className="col-2">
{temp}
</div>
)
}
const addToTotal = (price) => {
//we need to add the full prices to total so it can display
return (
Math.ceil(price / 36)
)
}
//this also works.
//const showParts = (activeIndex) => { return parts.map(part => part.active[activeIndex] && <div key={part.bodyPart}>{part.bodyPart}</div>) }
const showParts = (activeIndex) => {
return parts.map(
(part) => part.active[activeIndex] && (
<div key={part.bodyPart} className="row">
{/* body part */}
<div className="col-3">{part.bodyPart}</div>
{/* original full price */}
<div className="col-2 text-decoration-line-through">${addToTotal(part.price)}</div>
{/* discounted price */}
<div className="col-2">
${(showDiscounted(part.price))}
</div>
{/* choose discount */}
<div className="col-auto">
<select className="form-select-sm" aria-label="Default select example" /* value={discount} */ onChange={(e) => handleDiscountChange(part, e)}>
<option defaultValue>{discount}</option>
<option value="30">30%</option>
<option value="40">40%</option>
<option value="50">50%</option>
<option value="60">60%</option>
</select>
</div>
<div className="col-1 text-end"><button type="button" className="btn-close" aria-label="Close"></button></div>
</div>)
)
}
return (
<div className="">
<div className="row rows-cols-3">
{/* to keep this part cleaner, having a separate state that filters out is ideal */}
<div className="col-4">
{showParts(0)}
</div>
<div className="col-4">
{showParts(1)}
</div>
<div className="col-4">
{showParts(2)}
</div>
{/* TOTAL */}
<div className="col-4">discounted total for 36 months: {total[0]}</div>
<div className="col-4">total, per month, cost, savings</div>
<div className="col-4">total, per month, cost, savings</div>
</div>
</div>
);
}
export default QuoteCalcBot;
Can try to update the state from the event handler(source of the event)
//passing extra params to event handler
const handleDiscountChange = (part, e, partPrice) => {
// this may need extra props to update the price for the right part
const newDiscount = e.target.value
// put this in a common function
const calcPartPrice = Math.ceil((partPrice * ((100 - newDiscount) / 100)) / 36)
const newState = discountedTotal.map((t, i) => i === option? t + calcPartPrice : t)
setTotal(newState);
// update discount
console.log(e.target.value);
console.log(part);
}
better to move this outside as a seperate component
const ShowParts = ({ parts = [], activeIndex, discount, handleDiscountChange, addToTotal }) => {
return parts.map(
(part) => {
const partPrice = Math.ceil((part.price * ((100 - discount) / 100)) / 36)
return part.active[activeIndex] && (
<div key={part.bodyPart} className="row">
{/* body part */}
<div className="col-3">{part.bodyPart}</div>
{/* original full price */}
<div className="col-2 text-decoration-line-through">${addToTotal(part.price)}</div>
{/* discounted price */}
<div className="col-2">
<div className="col-2">
{partPrice}
</div>
</div>
{/* choose discount */}
<div className="col-auto">
<select className="form-select-sm" aria-label="Default select example" /* value={discount} */
// pass partPrice and other props needed to handler to help in total calculations
onChange={(e) => handleDiscountChange(part, e, part.price)}
>
<option defaultValue>{discount}</option>
<option value="30">30%</option>
<option value="40">40%</option>
<option value="50">50%</option>
<option value="60">60%</option>
</select>
</div>
<div className="col-1 text-end"><button type="button" className="btn-close" aria-label="Close"></button></div>
</div>)
})
}
usage inside main app
<div className="col-4">
<ShowParts
parts={parts}
activeIndex={0}
discount={discount}
handleDiscountChange={handleDiscountChange}
addToTotal={addToTotal}
/>
</div>
The code can be optimized further, but I hope this helps you in some way
How can I have a user set a function variable with an input number? I have a form they can enter a number into, but this needs to set the col var up top.
function DataFrame(){
var element = <li class="element"/>
var col <- the variable to be set by the form
var arr = []
var i
for (i = 0; i<row; i++){
arr.push(element)
}
const [toggle, setToggle] = React.useState(false);
const Element = () => <li className="element" />;
return (
<div>
<div >
<form>
<label>
input code:
<input type="number" name="dimension" />
</label>
<input type="submit" value="Submit" />
</form>
<div >
</div>
)
You may store it within local component's state (setting its initial value to some default one, e.g. 0):
const [col, setCol] = useState(0)
Then, upon input keyUp event, you may modify col by calling setCol with appropriate parameter:
<input type="number" name="dimension" onKeyUp={e => setCol(e.target.value)} />
I am fairly new to the Facebook's React world. Their documentation seems to be very good but there are a few areas where I need a little bit of clarity. This is one of them.
Src: http://tuts-javascript.appspot.com/reactjs-add-remove-table-row
var CompanyApp = React.createClass({
getInitialState: function() {
return {companylist:this.props.companies};
},
handleNewRowSubmit: function( newcompany ) {
this.setState( {companylist: this.state.companylist.concat([newcompany])} );
},
handleCompanyRemove: function( company ) {
var index = -1;
var clength = this.state.companylist.length;
for( var i = 0; i < clength; i++ ) {
if( this.state.companylist[i].cname === company.cname ) {
index = i;
break;
}
}
this.state.companylist.splice( index, 1 );
this.setState( {companylist: this.state.companylist} );
},
render: function() {
var tableStyle = {width: '100%'};
var leftTdStyle = {width: '50%',padding:'20px',verticalAlign: 'top'};
var rightTdStyle = {width: '50%',padding:'20px',verticalAlign: 'top'};
return (
<table style={tableStyle}>
<tr>
<td style={leftTdStyle}>
<CompanyList clist={this.state.companylist} onCompanyRemove={this.handleCompanyRemove}/>
</td>
<td style={rightTdStyle}>
<NewRow onRowSubmit={this.handleNewRowSubmit}/>
</td>
</tr>
</table>
);
}
});
var CompanyList = React.createClass({
handleCompanyRemove: function(company){
this.props.onCompanyRemove( company );
},
render: function() {
var companies = [];
var that = this; // TODO: Needs to find out why that = this made it work; Was getting error that onCompanyDelete is not undefined
this.props.clist.forEach(function(company) {
companies.push(<Company company={company} onCompanyDelete={that.handleCompanyRemove} /> );
});
return (
<div>
<h3>List of Companies</h3>
<table className="table table-striped">
<thead><tr><th>Company Name</th><th>Employees</th><th>Head Office</th><th>Action</th></tr></thead>
<tbody>{companies}</tbody>
</table>
</div>
);
}
});
var Company = React.createClass({
handleRemoveCompany: function() {
this.props.onCompanyDelete( this.props.company );
return false;
},
render: function() {
return (
<tr>
<td>{this.props.company.cname}</td>
<td>{this.props.company.ecount}</td>
<td>{this.props.company.hoffice}</td>
<td><input type="button" className="btn btn-primary" value="Remove" onClick={this.handleRemoveCompany}/></td>
</tr>
);
}
});
var NewRow = React.createClass({
handleSubmit: function() {
var cname = this.refs.cname.getDOMNode().value;
var ecount = this.refs.ecount.getDOMNode().value;
var hoffice = this.refs.hoffice.getDOMNode().value;
var newrow = {cname: cname, ecount: ecount, hoffice: hoffice };
this.props.onRowSubmit( newrow );
this.refs.cname.getDOMNode().value = '';
this.refs.ecount.getDOMNode().value = '';
this.refs.hoffice.getDOMNode().value = '';
return false;
},
render: function() {
var inputStyle = {padding:'12px'}
return (
<div className="well">
<h3>Add A Company</h3>
<form onSubmit={this.handleSubmit}>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Company Name" ref="cname"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Employee Count" ref="ecount"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Headoffice" ref="hoffice"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="submit" className="btn btn-primary" value="Add Company"/>
</div>
</form>
</div>
);
}
});
var defCompanies = [{cname:"Infosys Technologies",ecount:150000,hoffice:"Bangalore"},{cname:"TCS",ecount:140000,hoffice:"Mumbai"}];
React.renderComponent( <CompanyApp companies={defCompanies}/>, document.getElementById( "companyApp" ) );
This is a very good basic explanation of how ReactJS works. Thanks to the author.
But this comment,
var that = this; // TODO: Needs to find out why that = this made it work; Was getting error that onCompanyDelete is not undefined
Why is that necessary?
Is this the right way to do it? If not, what is?
Thanks in advance.
There's no mystery of "this" that is specific to ReactJS.
This is just a case of standard scoping issues that crop up with callbacks in JavaScript.
When you're in a react component, all methods on the base component will be scoped with the this being the current component, just like any other JavaScript "class".
In your snippet you have a render method which is a function on the base component and therefore this is equal to the component itself. However within that render method you're calling a callback with this.props.clist.forEach, any function callbacks inside the render method will need to be either bound to the correct this scope, or you can do var that = this (although this is an anti-pattern and should be discouraged)`.
Example, slightly simplified version of your snippet:
var MyComponent = React.createClass({
handleCompanyRemove: function(e) {
// ...
},
render: function() {
// this === MyComponent within this scope
this.props.someArray.forEach(function(item) {
// this !== MyComponent, therefore this.handleCompanyRemove cannot
// be called!
})
}
})
As you can see from the comments above, inside your callback for the .forEach you cannot use this directly without either defining a variable outside, or properly binding the function.
Other options to solve this are:
Binding the callback function to the correct this scope. Example:
this.props.someArray.forEach(function(item) {
// this === MyComponent within this scope too now!
// so you can call this.handleCompanyRemove with no problem
}.bind(this))
If you're using Babel/ES6 you can use the Fat Arrow function syntax which guarantees that this scope continues to the callback from the parent scope. Example:
this.props.someArray.forEach((item) => {
// this === MyComponent within this scope too now!
// so you can call this.handleCompanyRemove with no problem
})
Below is my code and I got a feeling I am really doing this wrong. I am new to react and I been spending many hours trying to figure this out with no luck.
I am trying to get users to input values of age, gender, height, weight etc..and then make the BMR box update with a value.
What i have so far is when the user clicks "Calculate for BMR" the onClick function spits out the correct result, but I have no clue how to get the value to appear in the "BMR input box" without any sort of refreshing.
Any help would be appreciated. Thanks
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleGenderChange = this.handleGenderChange.bind(this);
this.handleAgeChanged = this.handleAgeChanged.bind(this);
this.handleWeightChanged = this.handleWeightChanged.bind(this);
this.handleFeetChanged= this.handleFeetChanged.bind(this);
this.handleInchesChanged=this.handleInchesChanged.bind(this);
}
handleGenderChange = (event) => {
this.setState({Gender: event.target.value});
}
handleAgeChanged = (event) => {
this.setState({Age: event.target.value});
}
handleWeightChanged = (event) => {
this.setState({Weight: event.target.value});
}
handleHeightChanged = (event) => {
this.setState({Height: event.target.value});
}
handleFeetChanged = (event) => {
this.setState({Feet: event.target.value});
}
handleInchesChanged = (event) => {
this.setState({Inches: event.target.value});
}
onClick= (event) => {
event.preventDefault();
console.log(this.state);
const totalHeight = Number(this.state.Feet) * 12 + Number(this.state.Inches);
if(this.state.Gender == 'Male'){
var BMR = 66 + (6.23 * Number(this.state.Weight)) + (12.7 * totalHeight) - (6.8 * Number(this.state.Age));
console.log(BMR);
}
if(this.state.Gender == 'female'){
var BMR = 655 + (4.35 * Number(this.state.weight)) + (4.7 * totalHeight) - (4.7 * Number(this.state.age));
console.log(BMR);
}
}
render() {
return (
<div>
<Container>
<form>
<h3>Calories/TDEE Calculator</h3>
<div className="form-group">
<select className="form-control" value={this.state.Gender} onChange={this.handleGenderChange}>
<option disabled selected value> -- Gender-- </option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</div>
<div className="form-group">
<label htmlFor="Age">Age</label>
<input className="form-control"
onChange={this.handleAgeChanged}
type="input"
id="Age"
name="Age"
placeholder="Enter an Age"
value={this.state.Age}
/>
</div>
<div className="form-group">
<label htmlFor="Weight">Weight (lbs)</label>
<input className="form-control"
onChange={this.handleWeightChanged}
type="input"
id="Weight"
name="Weight"
placeholder="Enter Weight"
value={this.state.Weight}
/>
</div>
<div className="form-group">
<label>"Height (Ft/In)"</label>
<input type="input"
name="Feet"
placeholder="Feet"
onChange={this.handleFeetChanged}
value={this.state.Feet}
/>
<input type="input"
name="Inches"
placeholder="Inches"
onChange={this.handleInchesChanged}
value={this.state.Inches}
/>
</div>
<div className="form-group">
<label>BMR</label>
<input className="form-control"
id="BMR"
name="BMR"
value= ""
/>
</div>
<button className="btn btn-lg btn-primary btn-block" onClick={this.onClick.bind(this)}>Click for BMR</button> <br />
</form>
</Container>
</div>
);
}
}
export default Calculator;
EDIT:
Thanks everyone for taking your time to help, it worked :D. I learned from all your replies.
You don't have anything that renders this.state.BMR so it's not too surprising you don't see it anywhere. Some advice: don't use nonbreaking spaces and <br>: that's what CSS is for. Also, don't use bind, use arrow notation to preserve this, there is no reason to use all these bind calls in your constructor.
And then for the actual question: you need to actually render something, so have an element that either shows the button, or shows the BMR value, based on whether you computed it:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleGenderChange(evt) {
// you really should validate this
this.setState({Gender: event.target.value});
}
...
render() {
return <div>
...
<inputelement onChange={evt => this.handleInchesChanged(evt) } />
...
{ this.showButtonOrResult() }
</div>;
}
showButtonOrResult() {
// if the button wasn't clicked yet, then `BMR` will not yet be a state value
if (!this.state.BMR) {
return <button className="..." onClick={evt => this.onClick(evt)>Click for BMR</button>
}
// if it IS a state value, just render it
return <div>Your BMR is: { this.state.BMR }</div>;
}
}
So when your button is clicked, you do what you do, calculate BMR, then setState that, and render() automatically gets called again. Now there is a value to show, and instead of the button, it'll show a div with the result.
Also note that we are absolutely not using bind(this) in the constructor, because that's ridiculous. Properly handle your events with an arrow function so that you get the event, and then pass the event to the correct function, with normal this context.
You need to label your state properties correctly. Your female calculation is going to spit out NaN because you're using {this.state.weight} when you're setting it as 'Weight'
Initialize your state
this.state = {
bmr: ''
};
Set the value of your input
<div className="form-group">
<label>BMR</label>
<input className="form-control"
id="BMR"
name="BMR"
value={this.state.bmr}
/>
</div>
Set the state in your onclick function
onClick = (event) => {
event.preventDefault();
let BMR;
const totalHeight = Number(this.state.Feet) * 12 + Number(this.state.Inches);
if (this.state.Gender === 'Male') {
BMR = 66 + (6.23 * Number(this.state.Weight)) + (12.7 * totalHeight) - (6.8 * Number(this.state.Age));
this.setState({ bmr: BMR });
} else if (this.state.Gender === 'Female') {
BMR = 655 + (4.35 * Number(this.state.Weight)) + (4.7 * totalHeight) - (4.7 * Number(this.state.Age));
this.setState({bmr: BMR});
}
}
I can't seem to figure out why my form clears prev data when submitting. I have an edit button that when clicked pops open a form. If I edit the name field but not the birthdate field, the name is changed and the birthdate blanks out. It may be a simple silly error but a 2nd set of eyes may help
class Card extends Component {
constructor(props) {
super(props);
this.state = {
dataEditingMode: false,
planetSelection: this.props.homeWorld,
}
}
onEditDeets() {
this.setState({
dataEditingMode: !this.state.dataEditingMode
});
}
onSaveDeets(element) {
element.preventDefault();
this.props.onSavingEditedDeets(
this.props.id,
this.refs.personName.value,
this.refs.personBirthday.value,
this.refs.personHomeWorld.value)
this.setState({
dataEditingMode: false
});
}
onEditPlanetSelection(event) {
this.setState({
planetSelection:event.target.value
});
}
render() {
let getHomeWorld = (planetID) => {
for (var i = 0; i < this.props.planetList.length; i++) {
if (this.props.planetList[i].id === planetID) {
return this.props.planetList[i].name;
}
}
return 'planet does not exist.'
}
let name = this.props.name;
let imageURL = this.props.imageURL;
let birthday = this.props.birthday;
let homeWorld = this.props.homeWorld;
let dataEditingForm;
if (this.state.dataEditingMode === true) {
dataEditingForm = <form
onSubmit={this.onSaveDeets.bind(this)}>
<span>Name: </span>
<input type="text" ref="personName" />
<span>Birthday: </span>
<input type="text" ref="personBirthday" />
<span>Homeworld: </span>
<select
value={this.state.planetSelection}
ref="personHomeWorld"
onChange={this.onEditPlanetSelection.bind(this)}
>
{this.props.planetList.map((planet)=>{
return <option
key={planet.id}
value={planet.id}
>
{planet.name}
</option>
})}
</select>
<button>Save Deets</button>
</form>
} else {
dataEditingForm = <div></div>
}
return (
<div className='card'>
<div className='card-content'>
<div className='card-name'>{name}</div>
<img src={imageURL} alt='profile'/>
<p>
<span>Birthday:</span>
<span>{birthday}</span>
</p>
<p>
<span>Homeworld:</span>
<span>{getHomeWorld(homeWorld)}</span>
</p>
<p>
<span>
<button type="button" onClick={this.onEditDeets.bind(this)}>Edit Card Deets</button>
</span>
</p>
{dataEditingForm}
</div>
</div>
);
}
}
export default Card;
Basically, you are updating your state based on the values of the form, irrespective of whether they are changed or not.
For a simple change, you can just set the default value of your input tags to the state
<span>Name: </span>
<input type="text" ref="personName" defaultValue="{name}" />
<span>Birthday: </span>
<input type="text" ref="personBirthday" defaultValue="{birthday}"/>
Also, in this case, I prefer performing edits like this based on the form state but depending on the scenario you would want to handle onChange.
For example, in a settings page, you might want certain toggles to be effective immediately. Then you should handle onChange and update the state directly.