How to dynamically set State from Form input - javascript

I have 2 React parent/child components. The Child Component has a button that adds +1 to the previous state of the Parent Component, and a Form that triggers a handleChange function for the onChange event.
The Problem
From the Form input, I want to trigger a function that sets the State to the previous State, + the input in the Form.
For example, if I write 50 in input and hit submit I want the new state be 100
Here is a codesandbox: https://codesandbox.io/s/30mz2vvyo1
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 50
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState((prevState) => {
return { value: prevState.value + 1 }
});
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<div>
<Child value={this.state.value} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
</div>
)
}
}
class Child extends React.Component {
render() {
return (
<div>
<button onClick={this.props.handleChange}>Count + 1</button>
<div>{this.props.value}</div>
<form onSubmit={this.props.handleSubmit}>
<label>
Name:
<input type="text" onChange={this.props.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}

The problem you are facing can be mitigated by;
You need to have two different variables in state. value, can hold your value. You also need to hold the current value of the input, let's call it inputNumber.
You need to provide an onClick function to your button. In said function, set your state in the following fashion;
Code:
this.setState({
value: this.state.value + this.state.inputNumber,
})
After doing these things, it should work as expected.
I have updated your codesandbox, you can take a look at it here.

Related

React Javascript trouble with passing user input onchange to a parent component

I'm running into a problem when passing down down a method from a parent component to a child component. The parent, FilterableProductTable has a state called filterText. FilterableProductTable renders a child component SearchBar, and passes down a function called handleChange as a prop. SearchBar calls this function onChange, so that I can transfer the user input from SearchBar to the filterText state in FilterableProductTable.
The problem I'm running into is filterText gets updated one increment too late. I logged filterText to the console and I logged the value of the user input, both in my handleChange function:
handleChange(event) {
this.setState({ filterText: event.target.value });
console.log(event.target.value + "value");
console.log(this.state.filterText + "state");
}
and the output I get in the console is:
//user input=a.
a value
state
//user input=ab
ab value
a state
//user input =a, because b was deleted(backspace key)
a value
ab state
-----As you can see the state is one increment behind the event.target.value. I'm not sure how to fix this. Below are my two functions. If someone could help me see what I'm doing wrong that would be great.
class SearchBar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<form>
<input
type="text"
name="name"
placeholder="Search.."
onChange={this.props.handleChange}
></input>
<br />
<input type="checkbox" /> Only show items in stock
</form>
);
}
}
class FilterableProductTable extends React.Component {
constructor(props) {
super(props);
this.state = {
filterText: "",
inStockOnly: false,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ filterText: event.target.value });
console.log(event.target.value + " value");
console.log(this.state.filterText + " state");
}
render() {
console.log(this.state.filterText + " render");
return (
<div>
<SearchBar handleChange={this.handleChange} />
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
}
That's because setting state in React is an asynchronous operation and won't be affected immediately. you need to use the setState callback for your check like this:
handleChange(event){
this.setState({filterText:event.target.value}, () => {
console.log(event.target.value+ ' value');
console.log(this.state.filterText+ ' state');
});
}

Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler

I have Candidate list as a parent and Candidate as a child.
On Candidate list there is a Select All input so i bind function to it to set state if candidates are being selected and passing that state to child.
That one part works but those inputs are not mutable by itself just by parent Select All can be changed.
This is what does it looks like:
CandidateList Component(Parent):
class CandidateList extends React.Component {
constructor(props) {
super(props);
this.state = {
candidateList: null,
candidateSelected: false
}
}
onSelectCandidatesClick() {
this.setState({
candidateSelected: !this.state.candidateSelected
});
}
render() {
return (
<div className="CandidateList">
<div className="__selection">
<input className="checkbox" type="checkbox" id="selectAll" onClick={() => this.onSelectCandidatesClick()}/>
<label htmlFor="selectAll"><span id="__label">Select All</span></label>
</div>
</div>
);
}
}
Candidate Component(Child):
class Candidate extends React.Component {
constructor(props) {
super(props);
this.state = {
candidateSelected: false
}
}
componentWillReceiveProps(nextProps) {
this.setState({
candidateSelected: nextProps.selectCandidate
});
}
render() {
return (
<div className="Candidate">
<div className="__select">
<input className="checkbox" type="checkbox" id="candidate" data-candidate-id={this.props.candidateData.id} checked={this.state.candidateSelected} />
<label htmlFor="candidate"></label>
</div>
</div>
);
}
}
Thanks for all suggestions and help.
You need to give the checkbox input in your child component a way to process its change based on an input event.
<input checked={this.state.candidateSelected} onChange={e => {}} />
At the moment you're passing props in to say if it's checked or not so it can be an empty function.
Personally I think this is the wrong approach in any case. It's not an input because the user isn't inputting anything directly to that element.
Maybe you should have a prop called checked and if it is checked then show a check icon rather than an input.
{ this.props.checked ? <div className="checked"></div> : <div className="unchecked"></div>}
or something along those lines.
You can set the checked value of the child based on the props.candidateSelected (if the select all was selected from the parent)
or based on the state (if the checkbox was selected from the child)
You have to add an onclick event handler that will change the state when the checkbox in the child is clicked
class Candidate extends React.Component {
constructor(props) {
super(props);
this.state = {
candidateSelected: false
}
}
onSelectCandidatesClick() {
this.setState({
candidateSelected: !this.state.candidateSelected
});
}
render() {
return (
<div className="Candidate">
<div className="__select">
<input className="checkbox" type="checkbox" id="candidate" data-candidate-id={this.props.candidateData.id}
onClick={() => this.onSelectCandidatesClick()}
checked={this.state.candidateSelected || this.props.candidateSelected} />
<label htmlFor="candidate"></label>
</div>
</div>
);
}
}
This worked for me, but probably not the best option:
checked={isMenuOpen}
onClick={() => toggleDropdown()}
onChange={e => {}}
Have you made custom label for all inputs? 😃 this has gotten me before

Handle Input values when clicked React js

I was trying to handle changing of states whenever I type something inside the two text boxes and then when the user click the button, it will set the state to it's state and then console.log the current change state to the console.
Basically I have this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
catname: '',
catamt: 0
};
this.addBudget = this.addBudget.bind(this);
}
addBudget(e) {
e.preventDefault();
this.setState({
catname: e.target.value,
catamt: e.target.value
});
console.log('console log catname here.....', this.state.catname);
console.log('console log catamt here.....', this.state.catamt);
}
}
And then inside my component where the form is sitting:
import React from 'react';
export default class AddBudget extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="cat-input">
<input
type="text"
name="categoryname"
placeholder="Budget Category"
/>
<input
type="number"
name="categoryamount"
placeholder="Target Budget"
/>
</div>
<button onClick={this.addBudget}>+</button>
);
}
}
How do I pass along my input value to my function and console log the change of state?
Something more like that, I recommended using controlled input with react.
You can read more about it here https://reactjs.org/docs/forms.html
An example for you :) https://codesandbox.io/s/2486wxkn9n
First you need to keep track on the value with the state. Second with the form you can handle the submit. This way if a user click the button or press enter you can handle the submit method.
Inside the _handleChange method you receive the event. So this is the input change. If you console.log this value you can see he have the name, the name you pass in the input. This way you can use it as a key variable for your object. So one function for 2 :).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
catname: '',
catamt: 0
};
this.addBudget = this.addBudget.bind(this);
}
addBudget = (e) => {
e.preventDefault();
console.log('console log catname here.....', this.state.catname);
console.log('console log catamt here.....', this.state.catamt);
}
_handleChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<AddBudget handleChange={this._handleChange} addBudget={this.addBudget} />
)
}
}
export default class AddBudget extends React.Component {
render() {
return (
<div className="cat-input">
<form onSubmit={this.props.addBudget}>
<input
type="text"
name="catname"
onChange={this.props.handleChange}
placeholder="Budget Category"
/>
<input
type="number"
name="catamt"
placeholder="Target Budget"
onChange={this.props.handleChange}
/>
<button type="submit">+</button>
</form>
</div>
);
}
}

Saving the values in forms after leaving the page in reactjs

So I have a bunch of forms that the user can navigate back and forth from, but when he comes back the form should be as they left it, and all their values can be submitted at once.
I thought of keeping a large object, that temporarily stores the value for all the forms and is what is submitted by then end of it.
Whenever I return to the page, I just put that certain object in the value property.
The problem with this, is that once I return to the filled out form, I can't edit it anymore.
What can I do to make this happen? Also, I am willing to change it completely if there is a whole better way to do this.
Here is some relevant code with some comments explaining things:
// form3 only has to text fields, and form4 only has a file entry,
//which is why they aren't objects
//form1 and form2 on the other hand are huge and generate dynamically (I get
//all their fields through a get request and don't necessarily know
//how many there will be
export var formDataObject = {
form1:{},
form2:{},
form3:{
field1:"",
field2:""
},
form4:""
};
// for the next part I'll use form3 as an example since it is simple:
//The Form3 component is just a simple text input, I include the code
//of that too in case it's needed
export default class FullForm3 extends Component{
constructor(props){
super(props);
this.state ={
initial_field1: formDataObject.form3.field1,
initial_field2: formDataObject.form3.field2
}
}
render(){
var field1Value, field2Value;
if (this.state.initial_field1.length>0)
field1Value = formDataObject.form3.field1;
if (this.state.initial_field2.length>0)
field2Value = formDataObject.form3.field2;
return (
<div>
<Panel>
<Form3 controlId="field1Id" label={field1Label}
value={field1Value}/>
<br/><br/>
<Form3 controlId="field2Id" label={field2Label}
value={field2Value}/>
</div>
);
}
}
class Form3 extends Component{
constructor(props){
super(props);
this.state = {
value: formDataObject.form3,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const value = Object.assign({}, this.state.value);
value[e.target.id] = e.target.value;
this.setState({ value });
formDataObject.form3= this.state.value;
}
handleSubmit(e){
//things for submit (not important)
}
render(){
return (
<Form inline onSubmit={this.handleSubmit}>
<ControlLabel>{this.props.label}</ControlLabel>
<FormControl
type='text' label='Text'
value={this.props.value} onChange={this.handleChange}
/>
</FormGroup>
</Form>
)
}
}
I did it in a few steps:
Create controlled forms/inputs
OnInputChange save the formData for later use inside a callback from setState() (I used localStorage)
Get saved data in constructor(more readable in my example)/componentWillMount and setState of inputs to saved values
Now you can edit these inputs after refresh
Here you have full component with 2 inputs:
class SimpleInputs extends Component {
constructor(props) {
super(props);
if(localStorage.getItem('formData')) {
this.state = JSON.parse(localStorage.getItem('formData')); //in this case the state is just for input values
} else {
this.state = {
value1: '',
value2: ''
}
}
this.handleInput1Change = this.handleInput1Change.bind(this);
this.handleInput2Change = this.handleInput2Change.bind(this);
}
handleInput1Change(event) {
this.setState({ value1: event.target.value }, () => {
localStorage.setItem('formData', JSON.stringify(this.state));
});
}
handleInput2Change(event) {
this.setState({ value2: event.target.value }, () => {
localStorage.setItem('formData', JSON.stringify(this.state));
});
}
render() {
return(
<form>
<label>Name</label>
<input type="text" value={this.state.value1} onChange={this.handleInput1Change}/>
<label>Phone number</label>
<input type="text" value={this.state.value2} onChange={this.handleInput2Change}/>
</form>
);
}
}

How to avoid duplicate event listener in react?

I have a form in react with many input components. I do not like that I have to write a new onChange handler method for every input component that I build. So I want to know how can I stop repeated code.
<Input
label={"Blog Name"}
hint={"e.g. 'The Blog'"}
type={"text"}
value={this.state.name}
onChange={this.handleInputChange.bind(this, "name")}
/>
<Input
label={"Blog Description"}
hint={"e.g. 'The Blog Description'"}
type={"text"}
value={this.state.desc}
onChange={this.handleInputChange.bind(this, "desc")}
/>
So instead of writing a new function I am reusing the same function and passing an extra value. Is this the right way to do it? How do other experienced people solve this problem.
If you want your parent component to maintain the state with the value of each input field present in 'Input' child components, then you can achieve this with a single change handler in the following way:
handleChange(id, value) {
this.setState({
[id]: value
});
}
where the id and value are obtained from the Input component.
Here is a demo: http://codepen.io/PiotrBerebecki/pen/rrJXjK and the full code:
class App extends React.Component {
constructor() {
super();
this.state = {
input1: null,
input2: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id, value) {
this.setState({
[id]: value
});
}
render() {
return (
<div>
<Input id="input1"
changeHandler={this.handleChange} />
<Input id="input2"
changeHandler={this.handleChange} />
<p>See input1 in parent: {this.state.input1}</p>
<p>See input2 in parent: {this.state.input2}</p>
</div>
);
}
}
class Input extends React.Component {
constructor() {
super();
this.state = {
userInput: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const enteredText = event.target.valuel
this.setState({
userInput: enteredText
}, this.props.changeHandler(this.props.id, enteredText));
}
render() {
return (
<input type="text"
placeholder="input1 here..."
value={this.state.userInput}
onChange={this.handleChange} />
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
You can try event delegation, just like the traditional ways.
That is, just bind a function to the parent form element, and listens to all the events bubbling up from the children input elments.

Categories