onChange callback not firing in React component - javascript

My onChange() function does not run unless I use jQuery for some reason. The reason why I have to add the onChange listener in componentDidMount() is because I'm using MaterializeCSS which transforms your select tag into a ul. The code below works fine:
onChange(e) {
let inspectionTime = e.target.value;
this.setState({ inspectionTime });
}
componentDidMount() {
let $inspectionDropdown = $(ReactDOM.findDOMNode(this.refs.inspection));
$inspectionDropdown.on('change', this.onChange);
}
but this code does not:
onChange(e) {
let inspectionTime = e.target.value;
this.setState({ inspectionTime });
}
componentDidMount() {
let inspectionDropdown = ReactDOM.findDOMNode(this.refs.inspection);
inspectionDropdown.addEventListener('change', this.onChange);
}
Here is the code for the whole component if it helps at all:
import React from 'react';
import ReactDOM from 'react-dom';
class InspectionMode extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.defaultValue = 'selected';
this.state = { inspectionTime: 0 };
}
onChange(e) {
let inspectionTime = e.target.value;
this.setState({ inspectionTime });
}
componentDidMount() {
let inspectionDropdown = ReactDOM.findDOMNode(this.refs.inspection);
inspectionDropdown.addEventListener('change', this.onChange);
}
render() {
let classes = 'input-field col s10 offset-s1 l3';
return (
<div className={classes}>
<select onChange={this.onChange} ref="inspection" value={this.state.inspectionTime}>
<option value="0">None</option>
<option value='5'>5 Seconds</option>
<option value='10'>10 Seconds</option>
<option value='15'>15 Seconds</option>
</select>
<label>Inspection Time</label>
</div>
);
}
}
export default InspectionMode;

The problem is that the plugin is emitting a custom change event. React's event system doesn't recognize custom events (not sure why).
In this case a manual change listener is the right solution. The way you improve this is by abstracting the select element to an 'atom'.
class Select extends React.Component {
static propTypes = {
value: React.PropTypes.string,
options: React.PropTypes.arrayOf(
React.PropTypes.shape({
text: React.PropTypes.string.isRequired,
value: React.PropTypes.string.isRequired,
})
).isRequired
};
constructor() {
super();
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onChange(e.target.value);
}
componentDidMount() {
let select = this.refs.select;
select.addEventListener('change', this.onChange, false);
}
componentWillUnmount(){
let select = this.refs.select;
select.removeEventListener('change', this.onChange, false);
}
render() {
let classes = 'input-field col s10 offset-s1 l3';
return (
<div className={classes}>
<select ref="select" value={this.props.value}>
{this.props.options.map((x) => {
return <option key={x.value} value={x.value}>{x.text}</option>;
})}
</select>
</div>
);
}
}
You can then use this in InspectionMode or anywhere else in your UI.
class InspectionMode extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.value = 'selected';
this.state = { inspectionTime: 0 };
}
onChange(inspectionTime) {
this.setState({ inspectionTime });
}
render() {
return (
<div>
<Select
value={this.state.inspectionTime}
onChange={this.onChange}
options={[
{value: '0', text: 'None'},
{value: '5', text: '5 seconds'},
{value: '10', text: '10 seconds'},
{value: '15', text: '15 seconds'},
]}
/>
<label>Inspection Time</label>
</div>
);
}
}

Related

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate

There's already some people asking this question but they are almost caused by the same reason (fire the callback in situ e.g. <div onClick={this.handleClick()}></div>). However this doesn't happen to me but I got the same error.
P.S. Some util functions are defined behind the scene.
// Parent
export default class SearchArea extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
update: '',
confirmed: '',
recovered: '',
deaths: ''
},
showDashboard: false
}
this.setCountryData = this.setCountryData.bind(this);
this.showDashboard = this.showDashboard.bind(this);
}
setCountryData(data) {
console.log(data);
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
}
showDashboard(toggle) {
this.setState({ showDashboard: toggle })
}
render() {
return (
<div>
<Dropdown onSetCountryData={this.setCountryData} onShowDashboard={this.showDashboard} />
<Dashboard data={this.state.data} visible={this.state.showDashboard} />
</div>
)
}
}
// Sibling1
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
countries: [],
currentCountry: ''
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick() {
const showCountryData = Fetch(newFetchCountry).showJSONData;
showCountryData().then(res => {
const data = res[0];
this.passCountryData(data);
})
this.passToggleDashboard(true);
}
handleChange(e) {
this.setState({ currentCountry: e.target.value, });
this.passToggleDashboard(false);
}
passCountryData(data) {
this.props.onSetCountryData(data);
}
passToggleDashboard(toggle) {
this.props.onShowDashboard(toggle);
}
componentDidMount() {
let timer = setTimeout(() => {
const showCountryList = Fetch(fetchCountryList).showJSONData;
showCountryList().then(res => {
const data = res.map(country => country);
this.setState({ countries: data })
});
clearTimeout(timer);
}, 2000)
}
render() {
return (
<section className="dropdown-area">
<div className="dropdown">
<label htmlFor="country">Select country:</label>
<input list="countries" name="country" id="country" onChange={this.handleChange} />
<datalist id="countries" required>
{this.state.countries.length ?
this.state.countries.map(country => <option key={country.name}>{country.name}</option>) :
<option disabled>Loading</option>}
</datalist>
</div>
<button className="comp" onClick={this.handleClick}>Search</button>
</section>
)
}
}
// Sibling2
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
update: '',
confirmed: '',
recovered: '',
deaths: ''
}
};
}
componentDidMount() {
if (!('data' in this.props)) {
const showWorldData = Fetch(fetchWorld).showJSONData;
showWorldData().then(res => {
const data = res[0];
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
});
}
}
componentDidUpdate() { // Error here!!!
if ('data' in this.props) {
const data = this.props.data;
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
}
}
render() {
const visibility = {
visibility: 'visible' in this.props && !this.props.visible ? 'hidden' : 'visible'
};
return (
<section className="dashboard-area" style={visibility}>
<span>Last Update: {this.state.data.update || 'Loading...'}</span>
<div className="dashboards">
<DashboardItem num={this.state.data.confirmed} type="Confirmed" />
<DashboardItem num={this.state.data.recovered} type="Recovered" />
<DashboardItem num={this.state.data.deaths} type="Deaths" />
</div>
</section>
)
}
}
class DashboardItem extends React.Component {
render() {
return (
<div className="dashboard">{this.props.type}: <br /><span>{this.props.num || 'Loading...'}</span></div>
)
}
}
Error is in the componentDidMount() in the Dashboard component. I can't find where I fired the re-render infinitely.
The setState method is repeatedly updating the component because every time the 'data' in this.props equals to true you're calling setState and calling setState will by default update the component and componentDidUpdate will check again if 'data' in this.props equals to true and so
You should make strict conditions for if statement
try this
componentDidUpdate(prevProps, prevState) {
if ('data' in this.props && this.props.data !== prevProps.data) {
const data = this.props.data;
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
}
}
Your issue stems from derived state: state which is made dependent on props and is an anti-pattern in react.
This will tell you more about it:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
There are some work arounds, but its recommended you instead restructure your data flow.

How to change apply setState when onChange happend?

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);

how to reset the value of a dropdown to firstvalue after an action is perfomed

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>
)
}
}

How to pass the event.target.value from one component to another component using react

I have below Simple Dropdown component
import React,{Fragment} from 'react';
export default class SimpleDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
listOpen: false,
selectedValue: 'calculated Measure'
}
this.selectedItem = this.selectedItem.bind(this);
}
selectedItem = (event)=> {
this.setState({
selectedValue: event.target.value
})
}
render() {
return (
<Fragment>
<select className="dd-wrapper" value={this.state.selectedValue} onSelect={this.selectedItem} >
{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 file as and its state as
this.state = {
measures: [{
name: 'calculated Measure',
},{
name: 'Base Measure'
}]
}
<SimpleDropdown title="create Measure" list={this.state.measures} />
Now, I want to pass the selected value from simple dropdown component to here, How can I do that?
The basic idea is to create your selectedItem function from the component you want the value in. Pass that function as a prop to your dropdown as a handler and update the value in the component where you need that value. Notice below onSelect is now calling this.props.handleSelect.
import React,{Fragment} from 'react';
export default class SimpleDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
listOpen: false,
}
}
render() {
return (
<Fragment>
<select className="dd-wrapper" value={this.state.selectedValue} onSelect={this.props.handleSelect} >
{this.props.list.map((item) => (
<option className="dd-list-item" key={item.name} value={item.name}>{item.name}</option>)
)}
</select>
</Fragment>
);
}
}
I created another component class based on what you posted above to help clarify. But this is where you will define the function and pass it into SimpleDropdown.
export class TestComponent extends React.Component {
state = {
selectedDropDownValue: '',
[{
name: 'calculated Measure',
},
{
name: 'Base Measure'
}
]
}
handleDropDownSelect = (event) => {
this.setState({ selectedDropDownValue: event.target.value });
}
render() {
return <SimpleDropdown
title="create Measure"
list={this.state.measures}
handleSelect={this.handleDropDownSelect}
/>
}
}
Hopefully this helped! Let me know if you need any more clarification.

Why is a horizontal line created, when user inputs whitespace?

This is my code. When I'm trying to run it, for empty spaces as input it is creating a horizontal line.
import React, { Component } from 'react';
export default class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
render() {
return (
<div>
<form onSubmit = {this.handleSubmit}>
<input
onChange={this.handleChange}
value={this.state.text />
</form>
<div>
<TodoList items={this.state.items} />
</div>
</div>
);
}
handleChange(e) {
this.setState({ text: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.text.length) {
return;
}
const newItem = {
text: this.state.text,
};
this.setState(prevState => ({
items: prevState.items.concat(newItem),
text: ''
}));
}
}
class TodoList extends React.Component {
render() {
return (
<div>
{this.props.items.map(item => (
<h3 key={item.id}>{item.text}</h3>
))}
</div>
);
}
}
Your code will always append a <h3> element even with whitespace as input.
And you're seeing a horizontal line probably due to the CSS styling applied to
h3.
What you can do it prevent users from inserting whitespace data. One approach is to trim() user's input before doing length checking:
// In handleSubmit()
if (!this.state.text.trim().length) {
return;
}
Now input with only whitespace will become 0 length and therefore exit handleSubmit() earlier.
import React, { Component } from 'react'
class TodoList extends React.Component {
render() {
return (
<div>
{ this.props.items.map(item => <h3 key={ item.id }>{ item.text }</h3>) }
</div>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({ text: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.text.trim().length) {
return;
}
const newItem = {
text: this.state.text,
};
this.setState(prevState => ({
items: prevState.items.concat(newItem),
text: ''
}));
}
render() {
return (
<div>
<form onSubmit={ this.handleSubmit }>
<input onChange={ this.handleChange } value={ this.state.text }/>
</form>
<div>
<TodoList items={ this.state.items } />
</div>
</div>
)
}
}

Categories