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} />
Related
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');
});
}
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
How can I link to a value when selected onChange in a select box?
Looking to implement a select menu into ReactJS that links to the value onChange.
render() {
return (
<select onChange={() => {if (this.value) window.location.href=this.value}}>
<option value="">Please select</option>
{pages.map(({ node: page })=> (
<option key={page.id} value="{page.slug}">{page.title}</option>
))}
</select>
);
}
This is getting the value (I believe) but I keep getting the error of Cannot read property 'value' of undefined
I have tried following the documents here as suggested in some answers yet I have not been able to get this working with my current code - see as follows the full Page.js
import React from 'react'
import Helmet from 'react-helmet'
import styled from 'styled-components'
import config from '../utils/siteConfig'
const PageCompany = ({data}) => {
const {title,slug} = data.contentfulCompanyPage;
const pages = data.allContentfulCompanyPage.edges;
return(
<Wrapper>
<CompanyMenu>
<div>
<select onChange={() => {if (this.value) window.location.href=this.value}}>
<option value="">Please select</option>
{pages.map(({ node: page })=> (
<option key={page.id} value="{page.slug}">{page.title}</option>
))}
</select>
</div>
</CompanyMenu>
</Wrapper>
)
}
export const companyQuery = graphql`
query companyQuery($slug: String!) {
contentfulCompanyPage(slug: {eq: $slug}) {
title
slug
keywords
description
heroBg {
sizes(maxWidth: 1500) {
src
}
}
}
allContentfulCompanyPage(sort: {fields: [menuOrder], order: ASC}) {
edges {
node {
id
title
slug
}
}
}
}
`
export default PageCompany
Instead of making use of Global window.location property you can make a separate method handleChange like :
constructor(props) {
super(props);
this.state = { }; // initialise state
// Make sure to bind handleChange or you can make use of arrow function
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const targetValue = e.target.value;
// Then you can do whatever you want to do with the value
this.setState({
[name]: targetValue
});
EDIT : In order to make use of constructor make sure you are defining components using class syntax like:
import React , { Component } from 'react';
class PageCompany extends Component {
constructor(props) {
super(props);
this.state = { }; // initialise state
this.handleChange = this.handleChange.bind(this);
}
// Make sure class has a render method
render () {
return ()
}
}
And inside your <Select> You can reference it to handleChange
<select onChange={this.handleChange}>
You can read more about onChange Here
You need to pass the event param and then grab the value from the target of that event e.g.
onChange={(event) => this.setState({value: event.target.value})}
There's a great example here.
Full code excerpt from linked docs:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
I am trying to do two things when the child component's select element is changed-
update child state (used to display more fields and in other child-specific decisions etc.),
call parent handler passed in props.
Only 1st one is working, how do I add 2nd one also? Whats the proper way?
Dummy code:
class Child {
render(){
return(
<div>
//....
<select
id={this.state.searchId}
value={this.state.searchId}
onChange={
//1. event => this.setState({searchId : event.target.value})
//2. call parent props changeHandler
}>
{options}
</select>
//....
</div>
)
}
}
class Parent {
onSearchPrefChange(e) {
this.setState({
searchId : e.target.value
})
}
render(){
<Child onChange={this.onSearchPrefChange} />
}
}
I tried:
onChange={e => {
this.setState({searchId : e.target.value});
this.props.onSearchPrefChange;
}}>
The parent handler is not called, which also is used to change state of parent with same changed element's value/id.
You can define onChange event handler in the child component, set child's set there and call the callback passed from the parent in props from the function. Here is example code:
class Child extends React.Component {
constructor(props) {
super(props);
this.onSelectChange = this.onSelectChange.bind(this);
}
onSelectChange(e) {
const newValue = e.target.value;
this.props.onChange(newValue );
// set child state here
this.setState({searchId : newValue })
}
render() {
return (
<div>
<select onChange={this.onSelectChange}>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.someHandler = this.someHandler.bind(this);
}
someHandler(childValue) {
console.log(childValue);
this.setState({ value: childValue })
}
render() {
return <Child onChange={this.someHandler} />
}
}
Here is a working example on codesandbox.
I am trying to reset a input field on state update. So when my state updates through a function my view would change as well. Below is my code:
constructor(props){
super(props)
this.state = { song: '',
videos: '' }
this.handleSongInput = this.handleSongInput.bind(this)
}
in my render function I do something like this
render () {
return (
<div>
<TextField
floatingLabelText="Search Songs"
value={this.state.value}
onChange={this.handleSongInput}
/>
<br />
<RaisedButton label="Search" onClick={this.searchSong} />
</div>
)
}
The handle function for the Input field is below. It is simply setting the state.
handleSongInput = (e) => {
this.setState({ song: e.target.value})
}
Now on button click I have the following function which resets the initial
searchSong = () => {
...
this.setState({song:''})
}
Now if I do a console.log I can see that the state has changed. But in my view I can still see that the text field is populated with previous text.
How can I set the value of textfield with current state
I believe you have a variable name issue:
value={this.state.value}
should read:
value={this.state.song}