react input validation checking against last entered value instead of current value - javascript

I'm trying to validate my input , if the number is between 100 and 200 is should display valid or invalid , the problem i'm having is that it seems to be checking against the last entered value, so for instance if the user enters 1222 this will display valid as I believe it is actually checking against the last entered number of 122 and also if I then delete 2 charachers so it displays 12 this will also display valid. I believe this is because of how the state is set but I am not sure how to correctly get this to work.
How can I change this so it will check against the correct value and validate correctly?
Textbox.js
class TextBox extends React.Component {
state = {
valid: '',
value: ''
}
onChange = (event) => {
this.props.onChange(event.target.value);
this.validation()
}
validation() {
(this.props.value > 100 && this.props.value < 200) ? this.setState({value: this.props.value}) : this.setState({value: this.props.value})
}
onChangeInput(e) {
const { name, value } = e.target;
this.setState({
[name]: value
}, () => console.log(this.state.mail === this.state.confMail));
}
render() {
return (
<Box
invalid={!this.props.isValid}
>
<Label
rollo={this.props.designation === 'rollo'}
pleating={this.props.designation === 'pleating'}
>{this.props.label}</Label>
<span>
<Input
type={this.props.type && this.props.type}
defaultValue={this.props.defaultValue}
onChange={this.onChange}
placeholder={this.props.placeholder}
value={this.props.value || ''}
/>
<Tooltip>{this.state.valid}</Tooltip>
</span>
</Box>
);
}
};
export default TextBox;
component.js
<TextBox
type="number"
label="Fenstertyp"
defaultValue={ this.props.height / 100 * 66 }
onChange={newValue => this.props.selectOperationChainLength(newValue)}
tooltip="invalid"
value={this.props.operationChainLength.value}
/>
actions.js
export function selectOperationChainLength(operationChainLength) {
return {
type: SELECT_OPERATION_CHAIN_LENGTH,
operationChainLength
}
}

You can shift the validation logic to onChange method on event.target.value, there is no need to create the separate method. It will then look like this.
class TextBox extends React.Component {
state = {
valid: false,
value: ''
}
onChange = (event) => {
const value = event.target.value;
(value > 100 && value < 200) ? this.setState({value, valid: true}) : this.setState({value, valid: false})
this.props.onChange(value);
}
onChangeInput(e) {
const { name, value } = e.target;
this.setState({
[name]: value
}, () => console.log(this.state.mail === this.state.confMail));
}
render() {
return (
<Box
invalid={!this.props.isValid}
>
<Label
rollo={this.props.designation === 'rollo'}
pleating={this.props.designation === 'pleating'}
>{this.props.label}</Label>
<span>
<Input
type={this.props.type && this.props.type}
defaultValue={this.props.defaultValue}
onChange={this.onChange}
placeholder={this.props.placeholder}
value={this.props.value || ''}
/>
<Tooltip>{this.state.valid}</Tooltip>
</span>
</Box>
);
}
};
export default TextBox;

Ok, so there are some things going wrong here.
import React, { Component } from "react";
import { render } from "react-dom";
class TextBox extends Component {
constructor(props){
super(props);
// 1.
// You only need to store the `isValid` property.
// The value is needed only for the validation, right?
this.state = {
isValid: false
}
}
onChange(e) {
const { target } = e;
const { value } = target;
// 2.
// If the value is in the right range : isValid = true
// else : isValid = false
if( value > 100 && value < 200 ) {
this.setState({ isValid: true });
} else {
this.setState({ isValid: false });
}
}
render() {
// 3.
// Always use destructuring. It's way easier to follow ;)
const { type } = this.props;
const { isValid } = this.state;
return (
<Fragment>
<input
type={type}
onChange={e => this.onChange(e)}
/>
{/* 4. */}
{/* Assign the right text to your tooltip */}
<p>{ isValid ? "valid" : "invalid" }</p>
</Fragment>
);
}
}
ReactDOM.render(
<TextBox type="number" />,
document.getElementById('root')
);
<div id="root"></div>
<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>
I simplified the example so it can be easier to follow.
Here is a working example

I think you need to set your valid as false to begin with
state = {
valid: false,
value: ''
}

Related

React suggestions Input setting state

im prety new to React and im trying to use an autocomplete input. Im having problems getting the value from it and clearing the input values after submitting. Any help would be greatly appretiated.
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import "../AutoComplete/styles.css"
class Autocomplete extends Component {
static propTypes = {
suggestions: PropTypes.instanceOf(Array)
};
static defaultProps = {
suggestions: [],
};
constructor(props) {
super(props);
this.state = {
// The active selection's index
activeSuggestion: 0,
// The suggestions that match the user's input
filteredSuggestions: [],
// Whether or not the suggestion list is shown
showSuggestions: false,
// What the user has entered
userInput: this.props.value ? this.props.value : "",
};
}
//Order by 'code'
generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop]) return reverse ? -1 : 1;
if (a[prop] > b[prop]) return reverse ? 1 : -1;
return 0;
};
}
onChange = e => {
const { suggestions } = this.props;
const userInput = e.currentTarget.value;
// Filter our suggestions that don't contain the user's input
const filteredSuggestions = suggestions.sort(this.generateSortFn('code', true)).filter(
(suggestion, i) => {
let aux = suggestion.descrp+"- "+suggestion.code
return aux.toLowerCase().indexOf(userInput.toLowerCase()) > -1
}
);
this.setState({
activeSuggestion: 0,
filteredSuggestions,
showSuggestions: true,
userInput: e.currentTarget.value
});
};
onClick = e => {
this.setState({
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: e.currentTarget.innerText
});
};
onKeyDown = e => {
const { activeSuggestion, filteredSuggestions } = this.state;
// User pressed the enter key
if (e.keyCode === 13) {
this.setState({
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion].code+" - "+filteredSuggestions[activeSuggestion].descrp
});
}
// User pressed the up arrow
else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
this.setState({ activeSuggestion: activeSuggestion - 1 });
}
// User pressed the down arrow
else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
this.setState({ activeSuggestion: activeSuggestion + 1 });
}
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: {
activeSuggestion,
filteredSuggestions,
showSuggestions,
userInput
}
} = this;
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul className="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className="";
// Flag the active suggestion with a class
if (index === activeSuggestion) {
className = "suggestion-active";
}
return (
<li className={className} key={suggestion.code} onClick={onClick}>
{suggestion.code+" - "+suggestion.descrp}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div className="no-suggestions">
<p>Sin sugerencias</p>
</div>
);
}
}
and the return (this is where i think im wrong)
return (
<Fragment>
<label htmlFor="autocomplete-input" className="autocompleteLabel">{this.props.label}</label>
<div className="centerInput">
<input
className="autocomplete-input"
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
defaultValue={this.props.initState}
value= {/* this.props.value ? this.props.value : */ userInput}
placeholder={this.props.placeholder}
selection={this.setState(this.props.selection)}
/>
{suggestionsListComponent}
</div>
</Fragment>
);
}
}
export default Autocomplete;
What I want is to use this component in different pages, so im passing the "selection" prop and setting the state there.
The input is working correctly (searches, gets the value and shows/hide the helper perfectly). The problem is i cant reset this inputs clearing them, and i suspect the error is in here.
I get the following warning (even with it somewhat functioning)
Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
This is the Component usage with useState:
<Autocomplete label='Out cost Center:' placeholder='Set the out cost center' suggestions={dataCostCenterHelper} selection={(text) => setOutCostCenter(text.userInput)} value={outCostCenter} />
and last this is how im tryin to clear the state that is set in "selection":
const clearData = async () => {
setOutCostCenter('-');
// other inputs with the same component
setOutVendor('-');
setOutRefNumber('-');
}
This gets called inside the function that handles the button submitting the form.
Thanks in advance!
Looking at the code you posted this line might be the problem:
selection={this.setState(this.props.selection)}
You are updating state directly inside the render method, this is not recommended.
Try using a selection prop or state field and update the prop inside a componenteDidMount life cycle
selection={this.state.selection}

React does not call onchange callback of checkbox in jest test

I'm working on some code that uses a checkbox function (I've stripped off a lot of functionality)
export class MyLovelyCheckBox extends React.Component {
constructor(props) {
super(props);
if (this.props.isRequired) this.props.updateType('default');
}
handleChecked(checkbox) {
if (checkbox.checked) {
this.props.updateType(checkbox.value);
} else {
this.props.updateType('');
}
}
render() {
return <div>
<Checkbox
disabled = {
this.props.isRequired
}
checkboxType = 'default'
handleChecked = {
this.handleChecked.bind(this)
}
value = {
this.props.type
}
/>
</div>;
}
}
The Checkbox function is (again stripped back):
export default function Checkbox(props) {
const handleChange = (event) => {
props.handleChecked(event.target);
};
const { checkboxType } = props;
return (
<div>
<input
disabled={props.disabled}
id="thisCheckBox"
value={checkboxType}
checked={checkboxType === props.value}
type="checkbox"
onChange={handleChange}
aria-checked={checkboxType === props.value} />
<label
id="thisCheckBox-label"
htmlFor="thisCheckBox"
>
<Icon icon={checkboxType === props.value ? 'checkbox' : 'checkbox-outline'} size={20} viewBox={'0 0 20 20'}></Icon>
<span>{props.checkboxLabel}</span>
</label>
</div>
);
}
The problem comes from my tests, as I'm trying to make sure that the handleChecked code is covered by tests. To that end, I've written this test:
it('when clicked, updateTypes is called', () => {
const updateType = jest.fn().mockReturnValue(true);
const mountedComponent = create({ updateType }, false, false);
const checkBox = mountedComponent.find('Checkbox');
checkBox.simulate('change', { target: { checked: true } });
expect(updateType).toHaveBeenLastCalledWith(true);
});
Where the utility method create is:
const create = (params, required = {}, shallowlyRender = true) => {
const props = {
type: 'generic',
updateType: () => true,
};
const allProps = { ...props, ...params };
return shallowlyRender
? shallow(<MyLovelyCheckBox {...allProps} />)
: mount(
<Provider store={store}>
<MyLovelyCheckBox {...allProps}/>
</Provider>,
);
};
Despite my best efforts, I cannot get the handleChecked function to be called by the test.
Instead jest outputs:
expect(jest.fn()).toHaveBeenLastCalledWith(...expected)
Expected: true
Received: ""
Number of calls: 0
Where have I gone wrong on this test?

React updating state for validation always a step behind even when i set a callback function

I am trying to validate a form with two inputs. If each input has at least a character or more, the button should be enabled.
The problem here is that the state is always one step behind. I handled validation in the callback but it still didnt solve the problem. Please help anyone!
state = {
name : '',
nameIsValid: false,
zip : '',
zipIsValid: false,
allIsValid: false
}
handleChange = (event) => {
this.setState({
[event.target.name] : event.target.value,
}, ()=>this.handleValidation())
}
handleValidation = () => {
if(this.state.name.length > 0){
this.setState({nameIsValid : true})
} else {
this.setState({nameIsValid: false})
}
if(this.state.zip.length > 0){
this.setState({zipIsValid : true})
} else {
this.setState({zipIsValid: false})
}
if(this.state.nameIsValid && this.state.zipIsValid){
this.setState({allIsValid: true})
}
}
render() {
return (
<div>
Name: <input name="name" onChange={this.handleChange} value={this.state.name}/><br />
Zip: <input name="zip" onChange={this.handleChange} value={this.state.zip}/><br />
<button disabled={!this.state.allIsValid}>Proceed</button>
</div>
)
}
I think it's down to the multiple setStates you're using. You can actually make your code a little more concise and make this work at the same time.
class App extends React.Component {
state = {
name: '',
nameIsValid: false,
zip: '',
zipIsValid: false,
allIsValid: false
};
handleChange = (event) => {
// Destructure the name and value from the target
const { target: { name, value } } = event;
this.setState({
// Set only that changed input valid property to true or false
// depending on whether it has length
[`${name}IsValid`]: value.length > 0 || false,
[name]: value
}, this.validate);
}
validate = () => {
// Then just set the allIsValid property based on the
// whether the other properties are both true
this.setState((state) => ({
allIsValid: state.nameIsValid && state.zipIsValid
}));
}
render() {
return (
<div>
Name:
<input name="name" onChange={this.handleChange} value={this.state.name}/>
<br />
Zip:
<input name="zip" onChange={this.handleChange} value={this.state.zip} />
<br />
<button disabled={!this.state.allIsValid}>Proceed</button>
</div>
)
}
}
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>
state = {
name: '',
zip: ''
}
handleChange = event => {
this.setState({
[event.target.name]: event.target.value
});
}
validateForm = () => {
return this.state.name.length > 0 && this.state.zip.length > 0;
}
<button disabled={!this.validateForm()}>Proceed</button>

React: Number input with no negative, decimal, or zero value values

Consider an input of type number, I would like this number input to only allow a user to enter one positive, non-zero, integer (no decimals) number. A simple implementation using min and step looks like this:
class PositiveIntegerInput extends React.Component {
render () {
return <input type='number' min='1' step='1'></input>
}
}
ReactDOM.render(
<PositiveIntegerInput />,
document.getElementById('container')
)
<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>
<p>
Try to input a decimal or negative number or zero:
</p>
<div id="container"></div>
The above code works fine if a user sticks to ONLY clicking the up/down arrows in the number input, but as soon a the user starts using the keyboard they will have no problem entering numbers like -42, 3.14 and 0
Ok, lets try adding some onKeyDown handling to disallow this loophole:
class PositiveIntegerInput extends React.Component {
constructor (props) {
super(props)
this.handleKeypress = this.handleKeypress.bind(this)
}
handleKeypress (e) {
const characterCode = e.key
if (characterCode === 'Backspace') return
const characterNumber = Number(characterCode)
if (characterNumber >= 0 && characterNumber <= 9) {
if (e.currentTarget.value && e.currentTarget.value.length) {
return
} else if (characterNumber === 0) {
e.preventDefault()
}
} else {
e.preventDefault()
}
}
render () {
return (
<input type='number' onKeyDown={this.handleKeypress} min='1' step='1'></input>
)
}
}
ReactDOM.render(
<PositiveIntegerInput />,
document.getElementById('container')
)
<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>
<p>
Try to input a decimal or negative number or zero:
</p>
<div id="container"></div>
Now everything almost appears to work as desired. However if a user highlights all the digits in the text input and then types over this selection with a 0 the input will allow 0 to be entered as a value.
To fix this issue I added an onBlur function that checks if the input value is 0 and if so changes it to a 1:
class PositiveIntegerInput extends React.Component {
constructor (props) {
super(props)
this.handleKeypress = this.handleKeypress.bind(this)
this.handleBlur = this.handleBlur.bind(this)
}
handleBlur (e) {
if (e.currentTarget.value === '0') e.currentTarget.value = '1'
}
handleKeypress (e) {
const characterCode = e.key
if (characterCode === 'Backspace') return
const characterNumber = Number(characterCode)
if (characterNumber >= 0 && characterNumber <= 9) {
if (e.currentTarget.value && e.currentTarget.value.length) {
return
} else if (characterNumber === 0) {
e.preventDefault()
}
} else {
e.preventDefault()
}
}
render () {
return (
<input
type='number'
onKeyDown={this.handleKeypress}
onBlur={this.handleBlur}
min='1'
step='1'
></input>
)
}
}
ReactDOM.render(
<PositiveIntegerInput />,
document.getElementById('container')
);
<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>
<p>
Try to input a decimal or negative number or zero:
</p>
<div id="container"></div>
Is there a better way to implement a number input with this type of criteria? It seems pretty crazy to write all this overhead for an input to allow only positive, non-zero integers... there must be a better way.
If you did it as a controlled input with the value in component state, you could prevent updating state onChange if it didn't meet your criteria. e.g.
class PositiveInput extends React.Component {
state = {
value: ''
}
onChange = e => {
//replace non-digits with blank
const value = e.target.value.replace(/[^\d]/,'');
if(parseInt(value) !== 0) {
this.setState({ value });
}
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.onChange}
/>
);
}
}
Here's a number spinner implantation in React Bootstrap. It only accepts positive integers and you can set min, max and default values.
class NumberSpinner extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
oldVal: 0,
value: 0,
maxVal: 0,
minVal: 0
};
this.handleIncrease = this.handleIncrease.bind(this);
this.handleDecrease = this.handleDecrease.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
componentDidMount() {
this.setState({
value: this.props.value,
minVal: this.props.min,
maxVal: this.props.max
});
}
handleBlur() {
const blurVal = parseInt(this.state.value, 10);
if (isNaN(blurVal) || blurVal > this.state.maxVal || blurVal < this.state.minVal) {
this.setState({
value: this.state.oldVal
});
this.props.changeVal(this.state.oldVal, this.props.field);
}
}
handleChange(e) {
const re = /^[0-9\b]+$/;
if (e.target.value === '' || re.test(e.target.value)) {
const blurVal = parseInt(this.state.value, 10);
if (blurVal <= this.state.maxVal && blurVal >= this.state.minVal) {
this.setState({
value: e.target.value,
oldVal: this.state.value
});
this.props.changeVal(e.target.value, this.props.field);
} else {
this.setState({
value: this.state.oldVal
});
}
}
}
handleIncrease() {
const newVal = parseInt(this.state.value, 10) + 1;
if (newVal <= this.state.maxVal) {
this.setState({
value: newVal,
oldVal: this.state.value
});
this.props.changeVal(newVal, this.props.field);
};
}
handleDecrease() {
const newVal = parseInt(this.state.value, 10) - 1;
if (newVal >= this.state.minVal) {
this.setState({
value: newVal,
oldVal: this.state.value
});
this.props.changeVal(newVal, this.props.field);
};
}
render() {
return ( <
ReactBootstrap.ButtonGroup size = "sm"
aria-label = "number spinner"
className = "number-spinner" >
<
ReactBootstrap.Button variant = "secondary"
onClick = {
this.handleDecrease
} > - < /ReactBootstrap.Button> <
input value = {
this.state.value
}
onChange = {
this.handleChange
}
onBlur = {
this.handleBlur
}
/> <
ReactBootstrap.Button variant = "secondary"
onClick = {
this.handleIncrease
} > + < /ReactBootstrap.Button> < /
ReactBootstrap.ButtonGroup >
);
}
}
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
value1: 1,
value2: 12
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(value, field) {
this.setState({ [field]: value });
}
render() {
return (
<div>
<div>Accept numbers from 1 to 10 only</div>
< NumberSpinner changeVal = {
() => this.handleChange
}
value = {
this.state.value1
}
min = {
1
}
max = {
10
}
field = 'value1'
/ >
<br /><br />
<div>Accept numbers from 10 to 20 only</div>
< NumberSpinner changeVal = {
() => this.handleChange
}
value = {
this.state.value2
}
min = {
10
}
max = {
20
}
field = 'value2'
/ >
<br /><br />
<div>If the number is out of range, the blur event will replace it with the last valid number</div>
</div>);
}
}
ReactDOM.render( < App / > ,
document.getElementById('root')
);
.number-spinner {
margin: 2px;
}
.number-spinner input {
width: 30px;
text-align: center;
}
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/react-bootstrap#next/dist/react-bootstrap.min.js" crossorigin></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" crossorigin="anonymous">
<div id="root" />
That's how number input works. To simplify the code you could try to use validity state (if your target browsers support it)
onChange(e) {
if (!e.target.validity.badInput) {
this.setState(Number(e.target.value))
}
}
I had a similar problem when I need to allow only positive number, fount solution on another question on StackOverflow(https://stackoverflow.com/a/34783480/5646315).
Example code that I implemented for react-final-form.
P.S: it is not the most elegant solution.
onKeyDown: (e: React.KeyboardEvent) => {
if (!((e.keyCode > 95 && e.keyCode < 106) || (e.keyCode > 47 && e.keyCode < 58) || e.keyCode === 8)) {
e.preventDefault()
}
},
class BasketItem extends React.Component {
constructor(props) {
super(props);
this.state = {
countBasketItem: props.qnt,
};
}
componentDidMount() {
const $ = window.$;
// using jquery-styler-form(bad practice)
$('input[type="number"]').styler();
// minus 1
$(`#basket_${this.props.id} .jq-number__spin.minus`).click(() => {
if (this.state.countBasketItem > 1) {
this.setState({ countBasketItem: +this.state.countBasketItem - 1 });
this.setCountProduct();
}
});
// plus 1
$(`#basket_${this.props.id} .jq-number__spin.plus`).click(() => {
this.setState({ countBasketItem: +this.state.countBasketItem + 1 });
this.setCountProduct();
});
}
onChangeCount = (e) => {
let countBasketItem = +e.target.value
countBasketItem = (countBasketItem === 0) ? '' : (countBasketItem > 999) ? 999 : countBasketItem;
this.setState({ countBasketItem })
};
onBlurCount() {
// number empty
if (+this.state.countBasketItem == 0 || isNaN(+this.state.countBasketItem)) {
this.setState({ countBasketItem: 1 });
}
this.setCountProduct();
}
setCountProduct = (colrKey = this.props.colr.key, idProduct = this.props.product.id, qnt) => {
qnt = +this.state.countBasketItem || 1; // if don't work setState
this.props.basket.editCountProduct(idProduct, colrKey, qnt); // request on server
};
render() {
return;
<input
type="number"
className="number"
min="1"
value={this.state.countBasketItem}
onChange={this.onChangeCount.bind(this)}
// onFocused
onBlur={this.onBlurCount.bind(this)}
// input only numbers
onKeyPress={(event) => {
if (!/[0-9]/.test(event.key)) {
event.preventDefault();
}
}}
/>;
}
}
This is not a react problem, but a html problem as you can see over here https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number and I have made a stateless example you can see right here
https://codesandbox.io/s/l5k250m87

React.js - Show/Hide dynamically generated elements from component

I'm generating form field elements using a component template importing an array of data. I want to be able to hide some of these elements and show them when some other's element's criteria has been met; this is fairly common in form fields, e.g. When you select item A, form field X appears, when you select item B, form field X is hidden.
I've read a fair bit on conditional rendering on the React docs site but these examples don't really work for what I'm doing although the Preventing Component from Rendering section is close.
I made a Codepen demoing my setup, what I want to do is show the second field if the criteria for the first field is met (in this example, the first field should have 5 characters entered). I've passed through a prop to set the initial hiding but how can I find that specific hidden element and unhide it?
// Field data
const fieldData = [{
"type": "text",
"label": "First",
"name": "first",
"placeholder": "Enter first name",
"hidden": false
}, {
"type": "text",
"label": "Surname",
"name": "surname",
"placeholder": "Enter surname",
"hidden": true
}];
// Get form data
class FormData extends React.Component {
constructor(props) {
super(props);
this.state = {
items: props.data
};
}
render() {
let els = this.state.items;
return els.map((el, i) => {
return <Input key={i} params={el} />
});
}
}
// Input builder
class Input extends React.Component {
constructor(props) {
super(props);
// Keep state value
this.state = {
value: '',
valid: false,
hidden: this.props.params.hidden
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value,
valid: e.target.value.length < 5 ? false : true
});
}
render() {
// Element attributes
const {type, label, name, placeholder, hidden} = this.props.params;
const isValid = this.state.valid === true ? <span>Valid! Should show Surname field.</span> : <span>Not valid. Should hide Surname field.</span>;
if (!hidden) {
return (
<div>
{label ? <label htmlFor={name}>{label}</label> : null}
<input
type={type}
name={name}
placeholder={placeholder || null}
value={this.state.value}
onChange={this.handleChange}
/>
{isValid}
</div>
);
} else {
return null;
}
}
}
// App
class App extends React.Component {
render() {
return (
<div>
<h1>Show/Hide test</h1>
<p>What we want here is the surname to appear when firstname has a value (say, it has 5 characters) and hide surname when firstname doesn't.</p>
<FormData data={fieldData} />
</div>
);
}
}
ReactDOM.render(
<form>
<App />
</form>,
document.getElementById('app')
);
You can lift the state up so a parent of the Input will handle the state and validations.
You can conditionally invoke the "validator" of the surname property or any other property only if it exists and do a convention with yourself that a validate method will get the name of: propertNameValidator .
So for example when you do the loop over the inputs, you could check if there is
a validate method named surnameValidator and invoke it against the hidden prop that you will pass the Input, if it is not exists then just pass false.
Here is a small example with your code:
// Field data
const fieldData = [
{
type: "text",
label: "First",
name: "first",
placeholder: "Enter first name",
hidden: false
},
{
type: "text",
label: "Surname",
name: "surname",
placeholder: "Enter surname",
hidden: true
}
];
// Get form data
class FormData extends React.Component {
constructor(props) {
super(props);
this.state = {
items: props.data.map(el => ({...el, value: ''})) // add the value property
};
}
onInputChange = (inputId, value) => {
const { items } = this.state;
const nextState = items.map((item) => {
if (inputId !== item.name) return item;
return {
...item,
value,
}
});
this.setState({ items: nextState });
}
surnameValidator = () => {
const { items } = this.state;
const nameElement = items.find(item => item.name === 'first');
return nameElement && nameElement.value.length >= 5
}
render() {
let els = this.state.items;
return (
<div>
{
els.map((el, i) => {
const validator = this[`${el.name}Validator`];
return (
<Input
key={i}
{...el}
inputId={el.name}
hidden={validator ? !validator() : el.hidden}
onInputChange={this.onInputChange}
/>
);
})
}
</div>
)
}
}
// Input builder
class Input extends React.Component {
handleChange = ({ target }) => {
const { inputId, onInputChange } = this.props;
onInputChange(inputId, target.value);
}
render() {
// Element attributes
const { type, label, name, placeholder, hidden, value } = this.props;
return (
<div>
{
hidden ? '' : (
<div>
{label ? <label htmlFor={name}>{label}</label> : null}
<input
type={type}
name={name}
placeholder={placeholder || null}
value={value}
onChange={this.handleChange}
/>
</div>
)
}
</div>
);
}
}
// App
class App extends React.Component {
render() {
return (
<div>
<h1>Show/Hide test</h1>
<p>
What we want here is the surname to appear when firstname has a value
(say, it has 5 characters) and hide surname when firstname doesn't.
</p>
<FormData data={fieldData} />
</div>
);
}
}
ReactDOM.render(
<form>
<App />
</form>,
document.getElementById("app")
);
<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="app"></div>
If you already created the element and you just want to hide / show it, then you can conditionally add or remove a CSS class that hides it.
<input className={`${!isValid && 'hide'}`} />
If I'm not mistaken, what you want is to conditionally show or hide an input element. See if this helps.
render() {
// Element attributes
const {type, label, name, placeholder, hidden} = this.props.params;
const isValid = this.state.valid === true ? <span>Valid! Should show Surname field.</span> : <span>Not valid. Should hide Surname field.</span>;
if (!hidden) {
return (
<div>
{label ? <label htmlFor={name}>{label}</label> : null}
<input
type={type}
name={name}
placeholder={placeholder || null}
value={this.state.value}
onChange={this.handleChange}
/>
{this.state.valid && <input placeholder="add surname" />}
</div>
);
} else {
return null;
}
}

Categories

Resources