ReactJS: doing calculations to setState, and updating - javascript

I'd like to have two input numeric fields, value1 and value2 and add the values of those two fields. The sum will then be saved in this.state.sum. I also don't want to use a "submit" button. Some code:
import React, { Component } from 'react';
import addNumbers from "./components/addNumbers"
class App extends Component {
constructor() {
super()
this.state = {
value1: 10,
value2: 3,
sum: 13
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
//first setState call works fine
this.setState({[event.target.name]: event.target.value})
//call addNumbers function
const sum = addNumbers(this.state.value1, this.statevalue2)
//this second setState() call will lag by one step
this.setState({sum: sum})
console.log(newBarData)
}
render() {
return (
<div>
<form>
<input name="value1" type="number" placeholder="10" onBlur={this.handleChange} />
<input name="value2" type="number" placeholder="3" onBlur={this.handleChange} />
</form>
</div>
);
}
}
export default App;
What happens is: the second setState() call won't be effected until I make a second change on the page. This is quite a common issue, but I'm not sure how to work around it. I'd like for it to be able to proc immediately after the user updates the form. In the actual use-case, there'll be some calculations based on user-input values, then visualized on a graph (plotly.js), and I want to be able to update the graph "live" after each input field change.

You can use currying to create different handler in one piece of code (see: makeHandleChange). Then you can use componentDidUpdate to check if the value of your inputs changed to update your sum state value.
import React, { Component } from 'react';
import addNumbers from "./components/addNumbers"
class App extends Component {
state = {
value1: 10,
value2: 3,
sum: 13
}
componentDidUpdate(lastProps, lastState) {
const valueChanged = this.state.value1 !== lastState.value1 || this.state.value2 != lastState.value2
if (valueChanged) {
const sum = addNumbers(this.state.value1, this.state.value2)
this.setState({ sum })
}
}
makeHandleChange = id => ({ target: { value } }) => {
this.setState({
[`value${id}`]: value
})
}
render() {
return (
<div>
<form>
<input name="value1" type="number" placeholder="10" onBlur={this.makeHandleChange(1)} />
<input name="value2" type="number" placeholder="3" onBlur={this.makeHandleChange(2)} />
<div>the sum is: {this.state.sum}</div>
</form>
</div>
);
}
}
export default App;

Related

update parent's state fom child component using onChange

I am trying to update the number of event from the parent component by using an input form from the child component, but there is something I am not seeing it either doesn't work or shows undefined
class App extends Component {
state = {
numberOfEvents: 32,
};
.....
updateNumberOfEvents = (eventNumber) => {
this.setState({ numberOfEvents: eventNumber });
};
render() {
return (
<div className="App">
<NumberOfEvents updateNumberOfEvents={this.updateNumberOfEvents} />
}
</div>
class NumberOfEvents extends Component {
state = {
numberOfEvents: 32,
};
handleInputChanged = (event) => {
const value = event.target.value;
this.setState({
numberOfEvents: value,
});
this.props.updateNumberOfEvents(value);
};
render() {
const numberOfEvents = this.state.numberOfEvents;
return (
<div className="numberOfEvents">
<form>
<label for="fname"> Number of Events:</label>
<input
type="text"
className="EventsNumber"
value={numberOfEvents}
onChange={this.handleInputChanged}
/>
</form>
</div>
);
}
}
export default NumberOfEvents;
this.setState({
numberOfEvents: value,
}, () => {
this.props.updateNumberOfEvents(value);
}
);
The details are here.
While this answer does make it work and correctly highlights that setState calls are asynchronous, I would suggest removing the local state inside NumberOfEvents entirely, as you currently have multiple sources of truth for your form.
Update your onChange handler:
handleInputChanged = (event) => {
this.props.updateNumberOfEvents(event.target.value);
};
and pass down the value from the parent:
<NumberOfEvents
updateNumberOfEvents={this.updateNumberOfEvents}
numberOfEvents={this.state.numberOfEvents}
/>
and use that value inside your child component:
<input
type="text"
className="EventsNumber"
value={this.props.numberOfEvents}
onChange={this.handleInputChanged}
/>
Having one source of truth is less error prone and easier to maintain, as illustrated by your current bug.
I agree with hotpink but i tried your code on codesandbox...it does not show the value undfined. Here is the link https://codesandbox.io/s/stackoverflow-iow19?file=/src/App.js.
Check console

Empty value when form is submitted for the first time

I have the following code which is intended to get the input fed to ToggleForm component (which is a form) and store it in employeeData state. However, the problem is that whenever I press the submit button of ToggleForm for the first time after execution, "" value gets stored first in the employeeData state and it is only after I click the submit button for the second time that the data fed in the form comes to employeeData.
This must be a minor mistake. But I am not being able to figure it out.
import React from "react";
import ToggleForm from "./ToggleForm";
let employee = "";
class Home extends React.Component {
constructor(){
super();
this.state = {
employeeData: ""
};
}
addEmployee(e) {
e.preventDefault();
let name = e.target.name.value;
let address = e.target.address.value;
let salary = e.target.salary.value;
this.setState({
employeeData: [...this.state.employeeData, { name, address, salary }]
});
employee = [...this.state.employeeData];
console.log(employee);
}
render() {
return (
<div className="container">
<ToggleForm addEmployee={this.addEmployee.bind(this)}/>
</div>
);
}
}
export default Home;
Here is the ToggleForm component:
import React from 'react';
class ToggleForm extends React.Component {
render(){
return(<div>
<br/>
<h3>Add a new employee</h3>
<hr/>
<form className="form-group" onSubmit = {this.props.addEmployee}>
<input className="form-control" type="text" name="name" placeholder="Name of the employee"/><br/>
<input className="form-control" type="text" name="address" placeholder="Address of the employee"/><br/>
<input className="form-control" type="text" name="salary" placeholder="Salary of the employee"/><br/>
<input type="submit" className="btn btn-primary"/>
</form>
</div>)
}
}
export default ToggleForm;
setState is async and fortunately accepts an optional callback. Using the callback, you can access the most current value of state.
this.setState({
employeeData: [...this.state.employeeData, { name, address, salary }]
}, () => {
employee = [...this.state.employeeData];
});
Because setState is async so your need to setState in the component Toggle form when the text is change before ship it the parent component.
For example:
<input
onChange={this.handleChange}
className="form-control"
type="text"
name="name"
value={this.state.name}
placeholder="Name of the employee"
/>
<br />
Function handleChange:
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
console.log(e.target.value)
};
And then ship it to the parent:
handleSubmit = e => {
e.preventDefault();
const { name, address, salary } = this.state;
this.props.addEmployee({ name, address, salary });
};
Check my code here: https://codesandbox.io/s/ww5331jrxl
There are few basic correction in your components:
User super(); in the constructor before this.setState();
If you are not using this.state.employeeData, then don't set it in the state.
If you set the state then you will get the employeeData in the callback function as described by #Andy or you can use the following:
employee = [...this.state.employeeData, { name, address, salary }]

React - checkbox does not toggle

I am able to successfully change the discount amount from 0 to 50% off when the user checks the box off but it does not toggle off afterwards.
I'm assuming something is wrong with the handleChange function:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
// Define data above render()
constructor(){
super();
this.state = {
discount: 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
discount: event.target.checked = this.state.discount = .50});
}
render() {
return(
<div>
Apply 50% discount:
<input type='checkbox' value={this.state.discount} checked={this.state.discount} checked={this.state.discount} onChange={this.handleChange} />
<br/><br/>);
}
}
export default App;
It looks like you're missing an = in your handleChange function, which is causing the state update to behave incorrectly.
Try revising this function like so, for clarity and correctness:
handleChange(event) {
// Toggle the discount value between 0.5 and 0.0 via this logic
const nextDiscount = (this.state.discount === 0.5) ? 0.0 : 0.5
// Update discount state with nextDiscount value
this.setState({ discount: nextDiscount });
}
Also, consider updating your render() method so that the value for the checked prop evaluates to truthy/falsey based on the value of this.state.discount (ie via this expression (this.state.discount === 0.5)). Also ensure that only one checked prop is passed to that checkbox input:
render() {
return(<div>
Apply 50% discount:
<input type='checkbox'
value={this.state.discount}
checked={ (this.state.discount === 0.5) }
onChange={this.handleChange} />
<br/>
<br/>);
}

Inputs in child component won't update when prop is changing in parent component

I have a shopping list app that is divided to two components as follows:
I implemented those two components as: ShoppingList and:ItemDetails
There is another component: ListItem that represents one item row (with edit and delete buttons).
ShoppinList maps over an array of ListItems.
My App component fetches an initial items array and sends it to ShoppingList.
Each time a click is made on the edit icon in a specific item row I set selectedItem object in my app component and render the ItemDetails component, passing it the selectedItem like so:
toggleDetailsPanel = (itemId) => (e) => {
this.setState((prevState, props) => {
return {
selectedItem: (prevState.selectedItem && prevState.selectedItem.id === itemId) ? null : this.findItemById(itemId),
};
});
};
And in the App render function I render it like that:
<div className={styles.details_outer_container}>
{this.state.selectedItem ? <ItemDetails handleSubmit={this.saveDetails} item={this.state.selectedItem}/> : null}
</div>
Whenever a click is made on the save button I run a function on the app component that updates the item in the items array (saveDetails).
Now I expected the ItemDetails component to render with new values each time I click on a different edit icon in a different item row, but the inputs values won't change, only the title is rendering.
I tried all solutions that I found, involving defaultValue, or setting value with getValue() function, or setting a dynamic key on the inputs, but nothing really helps.
This is my ItemDetails file:
import React from 'react';
import PropTypes from 'prop-types';
import { Grid, Row, Col, input, Button } from 'react-bootstrap';
import styles from './styles.css';
export default class ProductDetails extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
item: PropTypes.any.isRequired,
};
state = {
id: this.props.item.id,
name: this.props.item.name,
quantity: this.props.item.quantity,
price: this.props.item.price,
description: this.props.item.description,
};
// Set appropriate property in state by input name
handleInputChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
// Submit changed item to parent component
handleDetailsSubmit = (e) => {
this.props.handleSubmit(this.state);
e.preventDefault();
};
render() {
const item = this.props.item;
const itemName = item.name.toUpperCase() || '';
return (
<div className={styles.details_container}>
<div className="sub_header">
<span>{`${itemName} DETAILS`}</span>
</div>
<form className={styles.form_style}>
<p>
<label>{'Quantity'}</label>
<input type="text" ref="quantity" name="quantity" value={this.state.quantity} onChange={this.handleInputChange} />
</p>
<p>
<label>{'Price'}</label>
<input type="text" ref="price" name="price" value={this.state.price} onChange={this.handleInputChange}/>
</p>
<p>
<label>{'Description'}</label>
<textarea rows={2} ref="description" name="description" value={this.state.description} onChange={this.handleInputChange}/>
</p>
<div className={styles.button_div}>
<Button onClick={this.handleDetailsSubmit} bsStyle="primary" bsSize="small">
{'Save'}
</Button>
</div>
</form>
</`enter code here`div>
);
}
}
I understand this is React's way of handling forms but really don't know how to solve it.
I would really appreciate any help : )
The ProductDetails component only gets its initial values from item. From that point it is all maintained in state. So you need to reset the state when item changes.
Try adding something like this:
componentWillReceiveProps( newProps ) {
if ( this.props.item !== newProps.item ) {
this.setState( {
id: newProps.item.id,
name: newProps.item.name,
quantity: newProps.item.quantity,
price: newProps.item.price,
description: newProps.item.description,
} )
}
}

Managing large forms - ReactJS

I was reading about react forms, and I just can read about two ways of managing data in forms.
The first one - refs, putting refs in each data input:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in this.textInput.
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}
and the second one Controlled Forms, putting a handler for each data input:
import React, { Component } from 'react';
import Form from 'react-form-controlled';
export default class Example extends Component {
constructor(props, context) {
super(props, context);
this.state = {
users: [{
firstName: 'Zlatko'
}, {
firstName: 'Livia'
}]
};
}
onChange = (data) => {
this.setState(data);
}
onSubmit = (data) => {
alert(`Hi ${data.users[0].firstName}`);
}
render() {
return (
<Form
value={this.state}
onChange={this.onChange}
onSubmit={this.onSubmit}
>
<fieldset name="users">
<label>
<input name="firstName" />
</label>
</fieldset>
<button type="submit">Submit</button>
</Form>
);
}
}
So, imagine you have a large form with a lot of data input, will you have to declare all the handler functions for each input in the form? (also, the total of bindings in the constructor)
Wouldn't it be convenient to just submit a just read form.input1, form.input2? I mean, somehing like this:
onSubmit(formValues){
payload = [
{'value1': formValues.input1 },
{'value2': formValues.input2 },
...
{'valueN': formValues.inputN },
]
}
without going to reading it from state?
I any case, is it and advantage to have a smart component with a lot of handlers for managing form values? or maybe the other approach, having a refs for each component in the form?
I would suggest to check redux + redux-form combo. Managing form state with these modules is piece of case. You can have functional components without any local state or handlers.

Categories

Resources