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');
});
}
Related
I have the following code where I am trying to update the value for the select tag.
constructor(props){
super(props)
this.state={value: 'Male'}
}
handleChange = (event) => {
this.setState({value: event.target.value})
this.props.selectCB(this.state.value)
console.log(this.state.value)
}
render(){
return (
<label>GENDER: <br/>
<select value={this.state.value} onChange={this.handleChange}>
<option value='Male'>Male</option>
<option value='Female'>Female</option>
<option value='Not Specified'>Not-Specified</option>
<option value='Non Binary'>Non-Binary</option>
</select>
<br/>
</label>
)
}
}
class NameForm extends React.Component{
constructor(props){
super(props)
this.state = {selectValue: ''}
}
handleSelectCallback = (selectData) => {
this.setState({selectValue: selectData})
}
handleSubmit = (event) => {
console.log('Logged select: ' + this.state.selectValue)
alert(`Submitted : ${this.state.selectValue}`)
event.preventDefault()
}
render(){
return <form onSubmit={this.handleSubmit}>
<SelectTag selectCB={this.handleSelectCallback}/>
<input type='submit' value='Submit'></input>
</form>
}
}
function App(){
return <NameForm/>
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(App());
The SelectTag is a child component to NameForm which in turn is rendered by the function App(). The change handler resides in SelectTag while the submit handler is in NameForm. I am trying to get the selected value from SelectTag to the parent NameForm by using a callback handleSelectCallback(). When the data in SelectTag is changed it is not being updated in NameForm.
If I start with the value Male and change it to Female, the value of selectValue in NameTag is still Male. If I change the value again (say to Not Specified), the value of selectValue changes to Female.
(Note: I noticed that this is working properly for other React components. I tested with components that render text boxes and text areas.)
You are sending the old state value through the callback. setState is async.
handleChange = (event) => {
// Schedule an update to the component with a new state value
this.setState({value: event.target.value})
// Callback with the outdated state value
this.props.selectCB(this.state.value)
}
You could just change it to the right value
handleChange = (event) => {
this.setState({value: event.target.value})
this.props.selectCB(event.target.value)
}
Or better yet, remove the state.value because it's not need. Instead keep the state in one place (the parent), and send the value and the callback down.
handleChange = (event) => {
this.props.selectCB(event.target.value)
}
// and
<select value={this.props.value} />
// and
<SelectTag selectCB={this.handleSelectCallback} value={this.state.selectValue} />
I recently got started with React and want to build a little application to fetch weather data. My API has a function to return autocomplete suggestions. So when my autosuggestion array is not empty I render a list and upon clicking one of the <li>'s I want the value inside of the input box. I manage to set the state of my SearchBar but can't change it's value.
Edit: I try to get my value from changeState() into my <input type="text" placeholder="City, Zip Code, Coordinates" onChange={evt => this.updateInputValue(evt)} />. I can search for terms otherwise.
import React from 'react';
import './SearchBar.css';
import Suggestion from './Suggestion';
class SearchBar extends React.Component{
constructor(props) {
super(props);
this.state = {inputValue: ''};
this.search = this.search.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.changeState = this.changeState.bind(this);
}
changeState(value) {
console.log(value);
// Logs value of text between <li></li>
this.setState({inputValue: value});
}
search() {
this.props.onSearch(this.state.inputValue);
}
updateInputValue(evt) {
this.setState({
inputValue: evt.target.value
});
this.props.onChange(this.state.inputValue);
}
handleKeyPress(e) {
if(e.key === 'Enter') {
this.search();
}
}
render() {
return (
<div>
<div className="SearchGroup" onKeyPress={this.handleKeyPress} >
<input type="text" placeholder="City, Zip Code, Coordinates" onChange={evt => this.updateInputValue(evt)} />
<a onClick={this.search}>Go</a>
</div>
<Suggestion autocomplete={this.props.autocomplete} onSelect={this.changeState} />
</div>
);
}
}
export default SearchBar;
For the sake of completeness my Suggestion.js:
import React from 'react';
import './Suggestion.css';
class Suggestion extends React.Component{
constructor(props) {
super(props);
this.updateInputField = this.updateInputField.bind(this);
}
updateInputField(evt) {
this.props.onSelect(evt.currentTarget.innerText);
}
render(){
if(this.props.autocomplete && this.props.autocomplete.length > 0) {
return (
<div className="Suggestion">
<ul>
{
this.props.autocomplete.map((location) => {
return (
<li key={location.id} onClick={this.updateInputField}>{location.name}</li>
)
})
}
</ul>
</div>
);
} else {
return <div className="None"></div>
}
}
}
export default Suggestion;
I would also prefer to submit location.url in Suggestion, but I could not find a property that matches inside of evt.
As mentioned in my comment. You are setting state and immediately passing state to onChange function in updateInputValue event handler function which is not correct. Because you won't get the state value updated immediately, the state value updates only when it renders so, pass evt.target.value directly like below
updateInputValue(evt) {
this.setState({ inputValue: evt.target.value });
this.props.onChange(evt.target.value);
}
In order to see chnaged value on your input field, you have to pass value prop to input tag like below
<input type="text" placeholder="City, Zip Code, Coordinates" onChange={evt => this.updateInputValue(evt)} value={this.state.inputValue}/>
I would guess that you are trying to use value from state that isnt there yet, because setState is asynchronous
so either use callback on setState
updateInputValue(evt) {
this.setState({
inputValue: evt.target.value
}, ()=> this.props.onChange(this.state.inputValue));
}
or, use the value from event directly
updateInputValue(evt) {
const value = evt.target.value
this.setState({
inputValue: value
});
this.props.onChange(value)
}
plus you havent assigned value back to your input:
<input type="text" placeholder="City, Zip Code, Coordinates" onChange={evt => this.updateInputValue(evt)} value={this.state.inputValue}/>
The React setState doesn't update the state immediately. It puts it in the queue and updates the state in batches. if you want to access the updated state write the code in the setState callBack
this.setState({ inputValue: evt.target.value},()=> this.props.onChange(this.state.inputValue));
something like this
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>
);
}
}
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.
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.