React: length validation, maximum update depth exceeded - reactjs

Fairly new to coding, and especially to React. Have seen other similar questions but can't seem to apply the answers to my code.
I'm trying to update the 'validate' state when the input length reaches 5 and I get the 'maximum update depth exceeded' error. From what I understand when the length reaches 5 it re-renders the DOM, finds that the length = 5 and so begins to call itself recursively (correct me if I'm wrong!), and so I'm trying to work out how to execute validationHandler() when the number reaches 5 only once.
class App extends Component {
state = {
inputLength: '0',
validate: 'Too short'
}
changeHandler = (event) => {
this.setState({ inputLength: event.target.value.length });
};
validationHandler = () => {
if (this.state.inputLength > 4) {
this.setState({ validate: "Enough"})
}
};
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler.bind(this)}
/>
<Validation
change={this.validationHandler()}
validate={this.state.validate}/>
</div>
);
}
}
I have a separate validation component.
const validation = (props) => {
return (
<div className="validation">
<p onChange={props.change}>{props.validate}</p>
</div>
)
};
Thanks in advance!

You have exactly the same issue as ReactJS: Maximum update depth exceeded error.
However, while applying the solution, i.e.
<Validation
change={this.validationHandler}
validate={this.state.validate}/>
will fix the error, it won't make your app work. The validationHandler method will never be called because p elements do not trigger a change event.
<p onChange={props.change}>{props.validate}</p>
Instead you want to validate the input length whenever the input changes, so that should happen inside the changeHandler method:
changeHandler = (event) => {
this.setState({
inputLength: event.target.value.length, // remove if not needed
validate: event.target.value.length > 4 ? "Enough" : "Too short",
});
};
and
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler}
/>
<Validation validate={this.state.validate}/>
</div>
);
}
Calling .bind(this) has no effect because this.changeHandler is an arrow function.

Change the code to below
class App extends Component {
state = {
validate: 'Too short'
}
changeHandler = (event) => {
if (event.target.value.length > 4) {
this.setState({ validate: "Enough"})
} else {
this.setState({ validate: "Too short"})
}
};
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler.bind(this)}
/>
<Validation
validate={this.state.validate}/>
</div>
);
}
}
and component to
const validation = (props) => {
return (
<div className="validation">
<p>{props.validate}</p>
</div>
)
};

Related

Multiple Events onChange with if/else statement

I'm using a bootstrap form slider from 1-5 to describe the condition of something from good - bad. On the slider change, I want it to envoke two functions; one to change the number in the state, and then one to identify the correct text to go with the number and display it as the user slides the slider. I've tried a few different variations of this, but nothing seems to work. Would also welcome other ways of forming the functions or state all together. Thanks in advance.
class ConditionSlider extends Component {
constructor (props) {
super(props);
this.state = {
condition: 1
};
};
render() {
const slideCalls = (e) => {
slideChangeState(e);
conditionText(e)
};
const slideChangeState = (e) => {
this.setState({
condition: e
})
}
let spanText;
const conditionText = (e) => {
if(e === '1' ) {
spanText = <div>Very Poor</div>
} else if (e === '2') {
spanText = <div>Poor</div>
} else if (e === '3') {
spanText = <div>Okay</div>
} else if (e === '4') {
spanText = <div>Good</div>
} else if (e === '5') {
spanText = <div>Excellent</div>
}
}
console.log(this.state.condition)
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{this.spanText}</p>
<p>{spanText}</p>
</div>
)}
};
You need to modify several things first to make it work. First, the render function should not contain those two functions. Then, you could have an object that contains the numbers as keys (1 to 5) and the string that correlates with that number (Poor, excelent, etc.) so you don't fallback to a large if...else statement.
So leaving it as this:
const RANGES = {
1: "Very Poor",
2: "Poor",
3: "Okay",
4: "Good",
5: "Excellent",
}
class ConditionSlider extends Component {
constructor (props) {
super(props);
this.state = {
condition: 1
};
};
slideCalls(e) {
slideChangeState(e);
conditionText(e)
};
slideChangeState(e) {
this.setState({
condition: e
})
}
render() {
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{RANGES[this.state.condition]}</p> // removed <div> here since you cant have a div inside a <p> element. I mean, you could but it would display an error in the console
<p>{spanText}</p>
</div>
)}
};
I'm coding this blind, but this is how I'd handle this situation. If you're using state to manage the value of the slider then you ,ight as well use state to hold the text value too.
I've moved the functions outside of the render method, and as slideChangeState requires access to this I've bound it in the constructor.
I hope this helps - never used this control before
class ConditionSlider extends Component {
constructor(props) {
super(props);
this.state = {
condition: 1,
spanText: "Very Poor"
};
this.slideChangeState = this.slideChangeState.bind(this);
};
slideChangeState(value) {
var spanText = conditionText(value);
this.setState({
condition: value,
spanText: spanText
})
};
conditionText(value) {
switch (value) {
case 1:
return "Very Poor";
case 2:
return "Poor";
case 3:
return "Okay";
case 4:
return "Good";
case 5:
return "Excellent"
}
}
render() {
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{this.state.spanText}</p>
</div>
)
}
};

reactjs multiple checkbox

I have the following code which add checkboxes in my page. The question is how can I get their values? What I am currently doing is using onChange to get the values and add it to my state but not sure if that's the best way? Whats the best practice in collecting data in input, should I use onChange or ref, I am a little bit confused with ref(I am new to ReactJS)
{Object.keys(this.state.IndustryChoice).map(key => (
<label className="custom-control fill-checkbox">
<input
name="Industry"
type="checkbox"
value={this.state.IndustryChoice[key]}
className="fill-control-input"
onChange={this.props.onMainToggle}
/>
<span className="fill-control-indicator" />
<span className="fill-control-description">
{this.state.IndustryChoice[key]}
</span>
</label>
))}
Here are other parts of my code
handleMainObjectCheckBoxToggle = event => {
let field = event.target.name;
let value = event.target.value;
let MainObject = this.state.MainObject;
// console.log("Winning Ways", MainObject[field]);
if (customIncludes(MainObject[field].results, value)) {
MainObject[field].results = customRemoveStringArray(
MainObject[field].results,
value
);
} else {
MainObject[field].results = appendObjTo(
MainObject[field].results,
value
);
// console.log(MainObject[field].results);
}
return this.setState({ MainObject });
};
<FormTactics
onMainToggle={this.handleMainObjectCheckBoxToggle}
/>
You must put checkbox values in your state as it gives your component more power to control the checkbox updates. Just use setState on onChange method and you'll see updated checkboxes
Let me know if you need more help
I can't quite understand your code but it seems you are trying to push or remove items somewhere in the state. I don't know if it suits you but I am providing a simple solution. Main differences with your code:
Hardcoded Industry key.
I'm not mutating the state directly which is a good behavior.
I am using the checked value for the elements.
I'm not quite sure how you implement this code in your app and if you need the checked values elsewhere. Here, we are keeping the checked values per value in the state in case you use them in the future.
class App extends React.Component {
state = {
IndustryChoice: {
foo: "foo",
bar: "bar",
baz: "baz"
},
MainObject: {
Industry: {
results: []
}
},
checkedValues: {}
};
handleMainObjectCheckBoxToggle = e => {
const { value, checked } = e.target;
const { results } = this.state.MainObject.Industry;
if (checked) {
const newResults = [...results, value];
this.setState(prevState => ({
MainObject: {
...prevState.MainObject,
Industry: { ...prevState.MainObject.Industry, results: newResults }
},
checkedValues: { ...prevState.checkedValues, [value]: checked }
}));
} else {
const newResults = results.filter(el => el !== value);
this.setState(prevState => ({
MainObject: {
...prevState.MainObject,
Industry: { ...prevState.MainObject.Industry, results: newResults }
},
checkedValues: { ...prevState.checkedValues, [value]: checked }
}));
}
};
render() {
return (
<div>
{Object.keys(this.state.IndustryChoice).map(key => (
<label>
<input
name={key}
type="checkbox"
value={this.state.IndustryChoice[key]}
className="fill-control-input"
onChange={this.handleMainObjectCheckBoxToggle}
/>
<span>{this.state.IndustryChoice[key]}</span>
</label>
))}
<p>results: {JSON.stringify(this.state.MainObject.Industry.results)}</p>
<p>checked: {JSON.stringify(this.state.checkedValues)}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

React/Redux Form - get ID of input onFocus and set state

I have a form with a single textarea. When text is entered into this textarea a new textarea should be displayed under the current one. If this new textarea has text entered then again another new one shows underneath (and on and on...).
In order to prevent a new textarea being added every time text is entered (for example if there are 3 textareas and the user focuses and changes the text in the first) I am storing the activeBulletPointId in my state, and when text is entered in it I am checking to see if it is the last bullet point in the array.
addNewBulletToEnd = () => {
let lastBulletId = this.state.data.slice(-1);
lastBulletId = lastBulletId[0].id;
if (this.state.activeBulletPointId === lastBulletId) {
const newBulletPoint = { id: this.generateId(), title: 'Click to add' };
this.setState({ data: this.state.data.concat(newBulletPoint) });
}
}
The issue I have is that when rendering my list I am unsure how to pass the id to the onFocus function.
handleFocus = (e) => {
console.log(e); //Can I get the ID here?
if (this.state.activeBulletPointId !== selectedBulletPointId) {
this.setState({ activeBulletPointId: selectedBulletPointId });
}
}
render() {
const bulletList = this.state.data.map((bulletPoint) => {
const reduxFormName = `${this.props.placeholder}-${bulletPoint.id}`;
return (
<div key={bulletPoint.id} className="bullet-point-input">
<SelectInputType
placeholder={reduxFormName}
type="textarea"
onChange={this.updateText}
onFocus={this.handleFocus}
handleKeyPress={this.handleKeyPress(reduxFormName)}
handleKeyDown={this.handleKeyDown}
noLabel
/>
</div>
);
});
return (
<div className="bullet-point-list">
{bulletList}
</div>
);
}
The <SelectInputType> component is what renders my redux-form <Field> component.
You could create a handler for each field. So you would avoid keeping data in DOM (as attributes) and keep it in handler's scope.
Unless you have hundreds of fields this wont hit overall performance.
setActiveBullet = activeBulletPointId => {
if (this.state.activeBulletPointId !== activeBulletPointId ) {
this.setState({ activeBulletPointId });
}
}
render() {
const bulletList = this.state.data.map((bulletPoint) => {
const reduxFormName = `${this.props.placeholder}-${bulletPoint.id}`;
return (
<div key={bulletPoint.id} className="bullet-point-input">
<SelectInputType
placeholder={reduxFormName}
type="textarea"
onChange={this.updateText}
onFocus={() => this.setActiveBullet(bulletPoint.id)}
handleKeyPress={this.handleKeyPress(reduxFormName)}
handleKeyDown={this.handleKeyDown}
noLabel
/>
</div>
);
});
return (
<div className="bullet-point-list">
{bulletList}
</div>
);
}

react / redux debounce throttling

I'm trying to debounce a component in my webapp. Actually it is a filter for the maxPrice and if the user starts to print, the filter starts to work and all the results disappear until there is a reasonable number behind it.
What I tried so far:
import _ from 'lodash'
class MaxPrice extends Component {
onSet = ({ target: { value }}) => {
if (isNaN(Number(value))) return;
this.setState({ value }, () => {
this.props.updateMaxPrice(value.trim());
});
};
render() {
const updateMaxPrice = _.debounce(e => {
this.onSet(e);
}, 1000);
return (
<div>
<ControlLabel>Preis bis</ControlLabel><br />
<FormControl type="text" className={utilStyles.fullWidth} placeholder="egal"
onChange={updateMaxPrice} value={this.props.maxPrice}
/>
</div>
);
}
}
I'm getting the error
MaxPrice.js:11 Uncaught TypeError: Cannot read property 'value' of null
at MaxPrice._this.onSet (MaxPrice.js:11)
at MaxPrice.js:21
at invokeFunc (lodash.js:10350)
at trailingEdge (lodash.js:10397)
at timerExpired (lodash.js:10385)
In my old version I had onChange={this.onSet} and it worked.
Any idea what might be wrong?
As you mentioned in comments, it's required to use event.persist() to use event object in async way:
https://facebook.github.io/react/docs/events.html
If you want to access the event properties in an asynchronous way, you
should call event.persist() on the event, which will remove the
synthetic event from the pool and allow references to the event to be
retained by user code.
It means such code, for example:
onChange={e => {
e.persist();
updateMaxPrice(e);
}}
Here is my final solution. Thanks to lunochkin!
I had to introduce a second redux variable so that the user see's the values he is entering. The second variable is debounced so that the WepApp waits a bit to update.
class MaxPrice extends Component {
updateMaxPriceRedux = _.debounce((value) => { // this can also dispatch a redux action
this.props.updateMaxPrice(value);
}, 500);
onSet = ({ target: { value }}) => {
console.log(value);
if (isNaN(Number(value))) return;
this.props.updateInternalMaxPrice(value.trim());
this.updateMaxPriceRedux(value.trim());
};
render() {
return (
<div>
<ControlLabel>Preis bis</ControlLabel><br />
<FormControl type="text" className={utilStyles.fullWidth} placeholder="egal"
onChange={e => {
e.persist();
this.onSet(e);
}} value={this.props.internalMaxPrice}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
maxPrice: state.maxPrice,
internalMaxPrice: state.internalMaxPrice
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({updateMaxPrice:updateMaxPrice,
updateInternalMaxPrice:updateInternalMaxPrice}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(MaxPrice);

e.target.value is undefined while adding dynamic input data in react

I need to add an input box (input-0, input-1...) each time a button is clicked.Following is the relevant code.
// State
this.state = {
newText: {}
};
// Code to add dynamic input text
addInputText = () => {
let dynamicTextsNo = Object.keys(this.state.newText).length;
let newInputId = dynamicTextsNo+1;
let dynamicTextArr = this.state.newText;
let newTextId = 'input-'+dynamicTextsNo;
dynamicTextArr[newTextId] = '';
let newState = { ...this.state, newText: dynamicTextArr }
this.setState( newState );
}
// Code to render dynamic input text.
dynamicTextArea = () => {
return Object.keys(this.state.newText).map( ( key ) => {
return ( <InputGroup key={key} borderType='underline'>
<Input placeholder='Type your text here' value={this.state.newText[key]} onChange={this.changeInput}/>
</InputGroup>
);
});
}
// Render function
render() {
return <View>{this.dynamicTextArea()}</View>
}
// handle input
changeInput = (e) => {
console.log( e.target.value ); // **this comes out to be undefined.**
}
Why is e.target.value in changeInput function undefined?
P.S. Jsfiddle link of the full code: https://jsfiddle.net/johnnash03/9by9qyct/1/
Unlike with the browser text input element, the event argument passed to React Native TextInput.onChange callback does not have a property called target.
Instead, use
<TextInput
onChange={(event) => event.nativeEvent.text}
/>
or
<TextInput
onChangeText={(text) => text}
/>
You must use bind like so <Input placeholder='Type your text here' value={this.state.newText[key]} onChange={this.changeInput.bind(this)}/>

Resources