I have a component which renders Input type='select': (I am using reactstrap)
import React, {Component} from 'react'
import {
Form,
FormGroup,
Input,
Button,
Col,
} from 'reactstrap'
import {
withRouter,
} from 'react-router'
import Context from '../../../../../provider'
class NewPost extends Component {
constructor(props) {
super(props)
this.state = {
subreddits: [],
subreddit_selected: '',
subreddit_id: 0,
...
}
this.handleSubredditSelect = this.handleSubredditSelect.bind(this)
}
componentDidMount() {
fetch('/api/reddit/r/')
.then(data => data.json())
.then(json => {
this.setState({
subreddits: json,
...
})
})
}
handleSubredditSelect(event) {
console.log('selected id: ',event.target.id)
this.setState({
subreddit_selected: event.target.value,
subreddit_id: event.target.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
...
render() {
return (
<Context.Consumer>
{context => {
return (
<React.Fragment>
<Form
...
>
<FormGroup row>
<Col sm={7}>
<Input
type="select"
onChange={this.handleSubredditSelect}
required
>
<option key='0' disabled selected>Select an Option</option>
{this.state.subreddits.map((subreddit) => {
return (
<option key={subreddit.id} id={subreddit.id}>{'r/' + subreddit.name}</option>
)
})}
</Input>
</Col>
</FormGroup>
...
</React.Fragment>
)
}}
</Context.Consumer>
)
}
}
export default withRouter(NewPost)
So, I have a function handleSubredditSelect which does the following:
handleSubredditSelect(event) {
this.setState({
subreddit_selected: event.target.value,
subreddit_id: event.target.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
In this function I am not getting any value for event.target.id.
I have tried event.target.key as well but that returned an empty string "".
I want to set subreddit_id in state to the selected option's ID
The selecting does not work because event.target in <select> element returns entire tree with options:
// result of event.target:
<select>
<option id="id-1" value="some1">some1</option>
<option id="id-2" value="some2">some2</option>
<option id="id-3" value="some3">some3</option>
</select>
Instead the selected one.
For accessing the current option element from select you should rely on selectedIndex:
event.target[event.target.selectedIndex].id
The code:
export default class SelectForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "some1",
id: "id-1"
};
}
handleChange = event => {
console.log("event", event.target, event.target.selectedIndex);
this.setState({
value: event.target.value,
id: event.target[event.target.selectedIndex].id
});
};
render() {
return (
<div>
<select value={this.state.sex} onChange={this.handleChange}>
<option id="id-1" value="some1">some1</option>
<option id="id-2" value="some2">some2</option>
<option id="id-3" value="some3">some3</option>
</select>
ID: {this.state.id}
</div>
);
}
}
You can use selectedIndex attribute of select:
handleSubredditSelect(event) {
const selectedOption = event.target.childNodes[event.target.selectedIndex];
this.setState({
subreddit_selected: event.target.value,
subreddit_id: selectedOption.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
Here is the sandbox: https://codesandbox.io/s/n5679m5owj
AFAIK event.target.id should be working, doing the same in my project.
But should't it be
<Input
type="select"
onChange={(e) => this.handleSubredditSelect}
required>`
? (No parantheses after the methodname)
Related
I'm a beginner in react-redux. I've implemented the dependant dropdown functionality in react component.(Country => State => City).
I want to update dropdown values when I receive data from redux state.
I am getting redux state in this.props.dropdownForm and based on that calling changeCountry, changeState, changeCity functions where I'm setting the selected country based on condition.
What problem I'm facing is this piece of code is not working:
componentDidMount() {
if (this.props.dropdownForm) {
this.changeCountry();
this.changeState();
// this.changeCity();
}
}
but when I put above code in setTimeout then it works updates the country dropdown
componentDidMount() {
setTimeout(() => {
if (this.props.dropdownForm) {
this.changeCountry();
this.changeState();
// this.changeCity();
}
}, 100);
}
but that too is not consistent, means sometime it works sometime it doesn't.
Not getting this weird behaviour.
Below is my code:
import React from 'react';
import { connect } from 'react-redux';
import { Col, FormGroup, Label, Input } from 'reactstrap';
import { createStructuredSelector } from 'reselect';
import { getDropdownData } from '../../redux/profile/profile.selectors';
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
countries: [
{ name: 'Germany', states: [{ name: 'A', cities: ['Duesseldorf', 'Leinfelden-Echterdingen', 'Eschborn'] }] },
{ name: 'Spain', states: [{ name: 'B', cities: ['Barcelona'] }] },
{ name: 'USA', states: [{ name: 'C', cities: ['Downers Grove'] }] },
{ name: 'Mexico', states: [{ name: 'D', cities: ['Puebla'] }] },
]
};
this.changeCountry = this.changeCountry.bind(this);
this.changeState = this.changeState.bind(this);
this.changeCity = this.changeCity.bind(this);
}
componentDidMount() {
if (this.props.dropdownForm) {
this.changeCountry();
// this.changeState();
// this.changeCity();
}
}
changeCountry(event) {
let countryVal = !event && this.props.dropdownForm ? this.props.dropdownForm.country : event.target.value;
this.setState({ selectedCountry: countryVal });
const result = this.state.countries.find(cntry => {
return cntry.name === countryVal
});
result ? this.setState({ states: result.states }) : this.setState({ states: null });
this.setState({ cities: null });
}
changeState(event) {
let stateVal = !event && this.props.statePropSelected ? this.props.statePropSelected : event.target.value;
this.setState({ selectedState: stateVal });
const stats = this.state.countries.find(cntry => {
return cntry.name === this.state.selectedCountry
}).states;
const result = stats.find(stat => stat.name === stateVal);
result ? this.setState({ cities: result.cities }) : this.setState({ cities: null });
}
changeCity(e) {
this.props.onChangeCity(e);
}
render() {
let country = this.state.selectedCountry;
let state = this.state.selectedState;
return (
<>
<FormGroup row>
<Col md="4">
<Label htmlFor={this.props.countryProp} className="required">{this.props.countryProp}</Label>
</Col>
<Col xs="12" md="8">
<Input type="select" name="country" id={this.props.countryProp} placeholder={this.props.countryProp} value={country} onChange={this.changeCountry}>
<option>Select Country</option>
{this.state.countries.map((e, key) => {
return <option key={key}>{e.name}</option>;
})}
</Input>
</Col>
</FormGroup>
<FormGroup row>
<Col md="4">
<Label htmlFor={this.props.stateProp} className="required">{this.props.stateProp}</Label>
</Col>
<Col xs="12" md="8">
<Input type="select" name="state" id={this.props.stateProp} placeholder={this.props.stateProp} value={state} onChange={this.changeState}>
<option>Select State</option>
{
this.state.states ? this.state.states.map((e, key) => {
return <option key={key}>{e.name}</option>;
}) : null
}
</Input>
</Col>
</FormGroup>
<FormGroup row>
<Col md="4">
<Label htmlFor={this.props.cityProp} className="required">{this.props.cityProp}</Label>
</Col>
<Col xs="12" md="8">
<Input type="select" name="city" id={this.props.cityProp} placeholder={this.props.cityProp} onChange={this.changeCity}>
<option>Select City</option>
{
this.state.cities ? this.state.cities.map((e, key) => {
return <option key={key}>{e}</option>;
}) : null
}
</Input>
</Col>
</FormGroup>
</>
)
}
}
const mapStateToProps = createStructuredSelector({
dropdownForm: getDropdownData
});
export default connect(mapStateToProps)(Dropdown);
Selector:
import { createSelector } from 'reselect';
const dropdown = (state) => {
return state.profile.items.personal_details;
};
export const getDropdownData = createSelector(
[dropdown],
(data) => data
);
In this case its better to use componentDidUpdate instead of componentDidMount. Since you are looking for an update of an specific property it's a good option.
Change this:
componentDidMount() {
if (this.props.dropdownForm) {
this.changeCountry();
// this.changeState();
// this.changeCity();
}
}
To this:
componentDidUpdate() {
if (this.props.dropdownForm && !this.state.countriesChanged) {
this.changeCountry();
this.setState({ countriesChanged: true });
// this.changeState();
// this.changeCity();
}
}
Edit note:
You may add a field in the state called "countriesUpdated" that works as a flag and set it to true when it execute that block.
This the code:
import React, { Component } from 'react';
import { Select } from 'antd';
import { connect } from "react-redux";
class SelecionarCrypto extends Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onSearch = this.onSearch.bind(this);
console.log(this.props);
this.state = {
ValorState: "nada"
}
};
onChange(value) {
console.log(`selected ${value}`);
this.setState({ValorState: value});
console.log("New value onchange", this.ValorState)
}
onBlur() {
console.log('blur');
}
onFocus() {
console.log('focus');
}
onSearch(val) {
console.log('search:', val);
}
render(){
const { Option } = Select;
console.log("New value Render", this.ValorState)
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Seleciona:"
optionFilterProp="children"
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option value="ETH">ETH</Option>
<Option value="BTC">BTC</Option>
<Option value="XRP">XRP</Option>
</Select>
);
}
}
const mapStateToProps = state => {
return {
token: state.token
};
};
export default connect(mapStateToProps)(SelecionarCrypto);
I am trying to change the value of ValorSate when onChange is done.
The error I am obtaining is: TypeError: this.setState is not a function.
I don´t find out the solution even readin about setSate() . I am followinf the same pattern of how-to´s or documentation but I no understanding something.
Now "New value onChange" or "New value Render" is always undefined"
console log:
Thank you.
I have modified your code. Please check it and try.
import React, { Component } from 'react';
import { Select } from 'antd';
import { connect } from "react-redux";
class SelecionarCrypto extends Component {
constructor(props) {
super(props);
console.log(this.props);
this.state = {
ValorState: 'nada'
}
};
onChange = (value) => {
console.log(`selected ${value}`);
this.setState({ValorState: 'algo'})
}
onBlur = () => {
console.log('blur');
}
onFocus = () => {
console.log('focus');
}
onSearch = (val) => {
console.log('search:', val);
}
render(){
const { Option } = Select;
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Seleciona:"
optionFilterProp="children"
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option value="ETH">ETH</Option>
<Option value="BTC">BTC</Option>
<Option value="XRP">XRP</Option>
</Select>
);
}
}
const mapStateToProps = state => {
return {
token: state.token
};
};
export default connect(mapStateToProps)(SelecionarCrypto);
Move those functions outside the render, bind them to the this of your component and reference them with the this keyword:
class SelecionarCrypto extends Component {
constructor(props) {
...
this.onChange = this.onChange.bind(this)
// Similar for the rest
}
onChange(value) { this.setState({ ValorState: value }) }
onBlur() {}
onFocus() {}
onSearch() {}
...
render(){
...
return
(
<Select
showSearch
style={{ width: 200 }}
placeholder="Seleciona:"
optionFilterProp="children"
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option value="ETH">ETH</Option>
<Option value="BTC">BTC</Option>
<Option value="XRP">XRP</Option>
</Select>
)
}
import React, { Component } from 'react';
import { Select } from 'antd';
import { connect } from "react-redux";
class SelecionarCrypto extends Component {
constructor(props) {
super(props);
//this.onChange = this.onChange.bind(this);
console.log(this.props);
this.state = {
ValorState: 'nada'
}
};
onChange=(value)=> {
console.log(`selected ${value}`);
this.setState({ValorState: 'algo'})
}
function onBlur() {
console.log('blur');
}
function onFocus() {
console.log('focus');
}
function onSearch(val) {
console.log('search:', val);
}
render(){
const { Option } = Select;
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Seleciona:"
optionFilterProp="children"
onChange={this.onChange}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option value="ETH">ETH</Option>
<Option value="BTC">BTC</Option>
<Option value="XRP">XRP</Option>
</Select>
);
}
}
const mapStateToProps = state => {
return {
token: state.token
};
};
export default connect(mapStateToProps)(SelecionarCrypto);
function must be a outside from render and bind onchange function otherwise setstate will not effective
import React from "react";
import { Select } from "antd";
import { connect } from "react-redux";
class SelecionarCrypto extends React.Component {
constructor(props) {
super(props);
//this.onChange = this.onChange.bind(this);
console.log(this.props);
this.state = {
ValorState: "nada",
};
}
onChange(value) {
console.log(`selected ${value}`);
this.setState({ ValorState: "algo" });
}
onBlur() {
console.log("blur");
}
onFocus() {
console.log("focus");
}
onSearch(val) {
console.log("search:", val);
}
render() {
const { Option } = Select;
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Seleciona:"
optionFilterProp="children"
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option value="ETH">ETH</Option>
<Option value="BTC">BTC</Option>
<Option value="XRP">XRP</Option>
</Select>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.token,
};
};
export default connect(mapStateToProps)(SelecionarCrypto);
I'm exploring React and am somewhat confused over lifecycle methods and parent-child communication. Specifically, I'm trying to create a component which wraps a select element and adds an input box when the "Other" option is selected. I have implemented this using getDerivedStateFromProps() but according to the documentation this lifecycle method should rarely be used. Hence my question: is there another pattern I should be aware of and use in this case?
This is my code, the value and options are passed down as props, as is the handleChange() method of the parent component. So when changes are made in the select or input elements, the parent component state is updated first and a new value is passed down through props.value.
export default class SelectOther extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
static getDerivedStateFromProps(props) {
let optionIndex = -1;
for (let i = 0; i < props.options.length; i++) {
if (props.options[i].value === props.value) {
optionIndex = i;
break;
}
}
if (optionIndex > -1) {
return {
selected: props.options[optionIndex].value,
text: "",
showInput: false
};
} else {
return {
selected: "",
text: props.value,
showInput: true
};
}
}
handleChange(e) {
this.props.handleChange({
"target": {
"name": this.props.name,
"value": e.target.value
}
});
}
render() {
return (
<div>
<label>{ this.props.label }</label>
<select name={ this.props.name } value={ this.state.selected } onChange={ this.handleChange }>
{
this.props.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)
}
<option value="">Other</option>
</select>
{
this.state.showInput &&
<div>
<label>{ this.props.label } (specify other)</label>
<input type="text" className="form-control" value={ this.state.text } onChange={ this.handleChange }></input>
</div>
}
</div>
)
}
}
You can simplify by not having SelectOther have any state, here is an example of how you can pass a function that dispatches an action to change values. Because SelectOther is a pure component it won't needlessly re render:
//make this a pure component so it won't re render
const SelectOther = React.memo(function SelectOther({
label,
name,
value,
options,
handleChange,
}) {
console.log('in render',name, value);
const showInput = !options
.map(o => o.value)
.includes(value);
return (
<div>
<label>{label}</label>
<select
name={name}
value={showInput ? '' : value}
onChange={handleChange}
>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
<option value="">Other</option>
</select>
{showInput && (
<div>
<label>{label} (specify other)</label>
<input
type="text"
name={name}
className="form-control"
value={value}
onChange={handleChange}
></input>
</div>
)}
</div>
);
});
const App = () => {
//create options once during App life cycle
const options = React.useMemo(
() => [
{ value: 'one', label: 'one label' },
{ value: 'two', label: 'two label' },
],
[]
);
//create a state to hold input values and provide
// a reducer to create new state based on actions
const [state, dispatch] = React.useReducer(
(state, { type, payload }) => {
//if type of action is change then change the
// payload.name field to payload.value
if (type === 'CHANGE') {
const { name, value } = payload;
return { ...state, [name]: value };
}
return state;
},
//initial state for the inputs
{
my_name: '',
other_input: options[0].value,
}
);
//use React.useCallback to create a callback
// function that doesn't change. This would be
// harder if you used useState instead of useReducer
const handleChange = React.useCallback(
({ target: { name, value } }) => {
dispatch({
type: 'CHANGE',
payload: {
name,
value,
},
});
},
[]
);
return (
<div>
<SelectOther
label="label"
name="my_name"
value={state.my_name}
options={options}
handleChange={handleChange}
/>
<SelectOther
label="other"
name="other_input"
value={state.other_input}
options={options}
handleChange={handleChange}
/>
</div>
);
};
//render app
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I could have uses useState in App but then I have to use useEventCallback or do a useState for every input value. The following documentation comes up with the useEventCallback pattern and then immediately after states that we don’t recommend this pattern so that's why I came up with the useReducer solution instead.
I have below simple dropdown component
import React, { Fragment } from 'react';
export default class SimpleDropdown extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => {
this.props.handleSelect(event);
}
render() {
return (
<Fragment>
<select className="dd-wrapper" onChange={this.handleChange}>
{this.props.list.map((item) => (
<option className="dd-list-item" key={item.name} value={item.name}>{item.name}</option>)
)}
</select>
</Fragment>
);
}
}
I'm using this component in some other place as below along with NewMeasureDialogue component.Now,When I select 'Base Measure' from the dropdown and clicked on NewMeasureDialogue 'onYesClicked'.The value from BaseMeasure should be changed to Calculated Measure.
export class ParentComponent {
constructor(props) {
super(props);
this.state = {
measures: [{
name: 'Calculated Measure',
},
{
name: 'Base Measure'
}
]
}
}
handleDropDownSelect = (event) => {
this.setState({
selectedValue: event.target.value,
isBaseMeasure: event.target.value === 'Base Measure' ? true : false
})
}
render() {
return (
<div>
<SimpleDropdown list={this.state.measures} handleSelect={this.handleDropDownSelect} />
<NewMeasureDialogue msg={StringConstants.NEW_MEASURE_DIALOGUE_TEXT} show={this.state.show} close={this.close} onYesClicked={this.onYesClicked} />
</div>
)
}
}
Can someone tell me how to change the dropdownvalue when onYesclicked is performed on NewMeasure Dialogue.
in order to achieve this, you may want to convert your SimpleDropdown into a fully controlled component, meaning it should accept not only handleSelect method, but also a value property
import React, { Fragment } from 'react';
export default class SimpleDropdown extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => {
this.props.handleSelect(event);
}
render() {
return (
<Fragment>
<select className="dd-wrapper" value={this.props.value} onChange={this.handleChange}>
{this.props.list.map((item) => (
<option
className="dd-list-item"
key={item.name}
value={item.name}
>
{item.name}
</option>
))}
</select>
</Fragment>
);
}
}
and do some modification in ParentComponent
export class ParentComponent {
constructor(props) {
super(props);
this.state = {
selectedValue: '',
measures: [{
name: 'Calculated Measure',
},
{
name: 'Base Measure'
}
]
}
}
handleDropDownSelect = (event) => {
this.setState({
selectedValue: event.target.value,
isBaseMeasure: event.target.value === 'Base Measure' ? true : false
})
}
onYesClicked = () => {
this.setState({selectedValue: 'Calculated Measure'})
}
render() {
return (
<div>
<SimpleDropdown
list={this.state.measures}
handleSelect={this.handleDropDownSelect}
value={this.state.selectedValue}
/>
<NewMeasureDialogue
msg={StringConstants.NEW_MEASURE_DIALOGUE_TEXT}
show={this.state.show}
close={this.close}
onYesClicked={this.onYesClicked}
/>
</div>
)
}
}
I've got a <select> element where I'm pulling the options from a Rails data model. This is ok, but produces a bog-standard HTML select dropdown.
However, I'm wanting to use react-select component and this is where I'm struggling. I am able to render the react-select dropdown, but the options are blank. I don't have any errors in the console, and I can see the 51 items in my array in React-Dev-Tools.
This is the code that produces the basic HTML dropdown.
import React from 'react';
import axios from 'axios';
import Select from 'react-select';
class Country extends React.Component {
constructor(props) {
super(props)
this.state = {
countries: []
}
}
getCountries() {
axios.get(`/countries.json`)
.then(res => {
const countries = res.data;
this.setState({ countries });
})
.catch(error => console.log(error))
}
componentDidMount() {
this.getCountries()
}
render() {
return (
<div className="container">
<select className="taskList">
{this.state.countries.map((country) => {
return (
<option key={country.id} value={country.id}>{country.country_name}</option>
)
})}
</select>
</div>
)
}
}
export default Country
This is the code I'm trying for the react-select, and doesn't work
import React from 'react';
import axios from 'axios';
import Select from 'react-select';
class Country extends React.Component {
constructor(props) {
super(props)
this.state = {
countries: []
}
}
getCountries() {
axios.get(`/countries.json`)
.then(res => {
const countries = res.data;
this.setState({ countries });
})
.catch(error => console.log(error))
}
componentDidMount() {
this.getCountries()
}
render() {
return (
let countryItems = this.state.countries.map((country) =>
<option key={country.id} value={country.id}>{country.country_name}</option>
);
return (
<div className="container">
<label>Country</label>
<Select id="country" name="coffee_beans[country_id]" options={countryItems} />
</div>
)
}
}
export default Country
Your options to react-select component should be an array of objects:
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
];
Now you are passing an array of components. So instead of
let countryItems = this.state.countries.map((country) =>
<option key={country.id} value={country.id}>
{country.country_name} .
</option>
);
try something along this:
let countryItems = this.state.countries.map(country => ({
value: country.id,
label: country.country_name
});