I have an input field component that meets these conditions:
Upon first focus, nothing happens until the field is valid, then a valid class is applied
Upon first blur, if the field is invalid, apply an invalid class.
After the first blur, upon any further engagement with the field, a class is applied whenever the value of the field changes from valid to invalid or vice versa.
To achieve this I have done this:
import React, { Component } from "react";
class Input extends Component {
constructor(props) {
super(props);
this.state = {
touched: false,
valid: false,
focused: false,
value: ""
};
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleFocus() {}
handleBlur() {
if (!this.state.touched) {
this.setState({
touched: true
});
} else if (this.state.touched) {
this.setState({
focused: true
});
}
}
handleChange(e) {
const val = e.target.value
this.setState({ value: val }, () => {
this.validateField();
}
);
}
validateField() {
const touched = this.state.touched;
const focused = this.state.focused;
const valid = this.state.valid;
const value = this.state.value;
if (value.length >= 5) {
this.setState({
valid: true,
touched: true
});
} else {
this.setState({ valid: false });
}
}
render() {
return (
<div>
<input
id={this.props.id}
name={this.props.name}
type="text"
className={`form-control ${styles["kyi-input"]} ${
this.state.valid ? "is-valid" : ""
} ${this.state.touched && !this.state.valid ? "is-invalid" : ""}`}
required
spellCheck="false"
autoComplete="off"
onFocus={this.handleFocus}
onChange={this.handleChange}
value={this.state.value}
onBlur={this.handleBlur}
placeholder={this.props.placeholder}
/>
</div>
);
}
}
class Parent extends Component {
handleInput(val, name) {
// Can get value of input here
this.setState({
[name]: val
});
}
render() {
<Input placeholder="Test" onChange={(val) => {this.handleInput(val, 'inputName')}}/>
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
It works, but this means that the state of the field lives in the child component and not the parent.
The input field's onBlur function is reliant on the state of the field.
Is there a way to refactor this so that the input state lives in the parent component while the onBlur function lives in the child component?
I think you should get all your State in the Parent component, you should also get all the functions that modify it in the Parent Component. This would allow you to have a 'single source of truth' that keeps track of state's changes and pass it to all your child components.
Check out Lifting state up
Related
I am try to change state in parent component onChange event.
On click on Checkbox state includeExtremeClaims should be changed into true.
Parent Component:
export class ManageAnalytics extends Component {
static contextTypes = baseContextTypes
state = {
includeExtremeClaims: false
}
handleChange = (e, { name, value } = {}) => {
this.setState({ [name]: value })
}
render() {
return (
<div>
// Passing down handleChange and includeExtremeClaims state as props
<Analytics
handleChange={this.handleChange}
includeExtremeClaims={this.state.includeExtremeClaims}
/>
</div>
)
}
Child Compnent:
export class Analytics extends Component {
static contextTypes = baseContextTypes
render() {
const { handleChange, includeExtremeClaims } = this.props
return (
<Checkbox
label="Include Extreme Cases"
name="includeExtremeClaims"
onChange={handleChange}
value={includeExtremeClaims}
/>
)
}
Question is: On click on Checkbox Why my state is not changed?
Problem was in value which is provide on Checkbox, value is always the same, that is reason why is not changed:
value={includeExtremeClaims} it should be value={!includeExtremeClaims}
I have 3 inputs whose value I save and click on my btn, I would like to clear these inputs.......
my function that saves the value of one of my inputs:
onChangeIdentity = (event) => {
this.newPlayer = Object.assign({}, this.newPlayer, { strPlayer:
event.target.value})
}
my input:
<Input style={{width:'30%'}} onChange={ this.onChangeIdentity }
ref='myFormRef' value={ this.newPlayer.strPlayer } type='text'
placeholder='Nom & Prenom'/>
and the function that must clear my input:
addPlayer = () => {
console.log('my new Player: ' , this.newPlayer);
this.setState({
teamPlayers: [...this.state.teamPlayers, this.newPlayer]
})
this.refs.myFormRef.value = ""
}
I tried several ways to declare my refs but nothing works.....
any ideas?
You input's values are driven by the state of the component value={this.newPlayer.strPlayer}. If you want to clear the input's value you need to clear the state which maps to it, for example:
this.setState({newPlayer: {strPlayer: ''}});
After setting the state, the component updates automatically and renders the input as empty.
Here is a full example component:
class MyComponent extends Component {
state = {
inputValue: ""
};
render() {
return (
<div>
<input
type="text"
value={this.state.inputValue}
onChange={event => this.setState({ inputValue: event.target.value })}
/>
<button
onClick={() => {
/* submit this.state.inputValue */
this.setState({ inputValue: "" }); // reset input value
}}
>
submit
</button>
</div>
);
}
}
I have the following problem.
I have customized my own DropDown component using elements.
I want this element to interact with Redux-Form as I want to save the value that is selected.
This does not work:
<Field
name="name"
component={MyCustomizedDropDown}
data={myData}/>
The other option was to use the "input" props but as I am using elements, this is not possible.
Can someone give me a solution? Thanks.
MyCustomizedDropDown component:
import React, { Component } from "react";
import PropTypes from "prop-types";
class MyCustomizedDropdown extends Component {
constructor(props) {
super(props);
this.state = {
...this.props,
items: this.props.items || [],
selectedItem: this.props.items[0] || this.props.selectedItem,
showItems: false,
isOpened: false
};
this.dropDown = this.dropDown.bind(this);
this.selectedItem = this.selectedItem.bind(this);
}
dropDown() {
this.setState(prevState => ({
showItems: !prevState.showItems
}));
}
selectedItem(item) {
this.setState({
selectedItem: item,
showItems: false
});
}
render() {
const { input } = this.props;
return (
<div className="select-box--wrapper">
<div className="select-box--toggle" onClick={this.dropDown}>
<div className="select-box--selected-item">
{this.state.selectedItem && this.state.selectedItem.value}
</div>
<MyImage
className={`${
this.state.showItems
? "select-box--arrow-rotated"
: "select-box--arrow"
}`}
/>
</div>
<div className="select-box--main">
<div
{...input} \\THIS DOES NOT WORK
className="select-box--items">
{this.state.data.map(item => (
<div key={item.id} onClick={() => this.selectedItem(item)}>
{item.value}
</div>
))}
</div>
</div>
</div>
);
}
}
MyCustomizedDropdown.propTypes = {
data: PropTypes.array,
selectedItem: PropTypes.array,
input: PropTypes.object
};
export default MyCustomizedDropdown;
redux-form only works with "controlled" components. That means the component needs a prop that a parent uses to tell it what it's value is. For example, the following is a controlled component:
<TextField
value={this.state.inputValue}
onChange={(value) => this.setState({ inputValue: value })}
/>
Note that we're telling the TextField component what it's value is. You need to change your component to work the same way. The only caveat here is that redux-form injects a prop called input that is an object containing value and onChange (and a few other things), instead of directly injecting value and onChange.
So for the example above, it needs to work like this to support redux-form:
<TextField
input={{
value: this.state.inputValue,
onChange: (value) => this.setState({ inputValue: value })
}}
/>
Here's your component written as a "controlled" component, in a way that should work with redux-form:
import React, { Component } from "react";
import PropTypes from "prop-types";
class MyCustomizedDropdown extends Component {
constructor(props) {
super(props);
this.state = {
showItems: false
};
this.dropDown = this.dropDown.bind(this);
this.selectedItem = this.selectedItem.bind(this);
}
dropDown() {
this.setState(prevState => ({
showItems: !prevState.showItems
}));
}
hideDropdownItems() {
this.setState({
showItems: false
});
}
render() {
const { input } = this.props;
return (
<div className="select-box--wrapper">
<div className="select-box--toggle" onClick={this.dropDown}>
<div className="select-box--selected-item">
{this.input.value && this.input.value.value}
</div>
<MyImage
className={`${
this.state.showItems
? "select-box--arrow-rotated"
: "select-box--arrow"
}`}
/>
</div>
<div className="select-box--main">
<div
className="select-box--items">
{this.state.data.map(item => (
<div
key={item.id}
onClick={() => {
this.input.onChange(item)
this.hideDropdownItems();
}}
>
{item.value}
</div>
))}
</div>
</div>
</div>
);
}
}
MyCustomizedDropdown.propTypes = {
data: PropTypes.array,
selectedItem: PropTypes.array,
input: PropTypes.object
};
export default MyCustomizedDropdown;
Note we tell MyCustomizedDropdown what it's value is using this.props.input.value
We call this.props.input.onChange if the component wants to change it's value. Since it can't do it on it's own, it needs to tell it's parent it wants to change value.
The parent needs to respond to onChange and update MyCustomizedDropdown's value
For example, this is how you'd use your component without redux-form:
<MyCustomizedDropdown
input={{
value: this.state.dropDownValue,
onChange: (value) => this.setState({ dropDownValue: value })
}}
/>
And with redux-form, you can simply do the following, since redux-form manages all that stuff for you:
<Field
component={MyCustomizedDropdown}
/>
You shouldn't be handling the value of the input in the input's state. MyCustomizedDropDown should receive the handleChange function, items and selectedItem as props. The only thing that should be in the component's state is it's open or not.
I need to empty an input field in my react component after the content has been stored to the DB. This is what I did so far:
addPost (event) {
const content = event.target.value
methodInsert.call(
{ content },
(error) => {
if (!error) {
event.target.value = ''
}
}
)
}
render()
<Input
onBlur={this.addPost.bind(this)}
/>
I am getting the error
Warning: This synthetic event is reused for performance reasons.
If you're seeing this, you're accessing the property `target` on a released/nullified synthetic event. This is set to null.
If you must keep the original synthetic event around, use event.persist().
You will need an onChange handler on your input that updates the state of the component when anything changes. You'll also need a onSubmit handler to handle your submit logic and then clear the input by using this.setState to set it's value to empty.
I recommend you read about Controlled Components in the React documentation.
Here is an example of how this can be accomplished:
class Example extends React.PureComponent {
constructor( props ) {
super( props );
this.state = {
inputValue = ""
}
this.handleChange = this.handleChange.bind( this );
this.handleSubmit = this.handleSubmit.bind( this );
}
handleChange( e ) {
this.setState({
inputValue: e.target.value
});
}
handleSubmit( e ) {
e.preventDefault();
// .. do your stuff and then
this.setState({
inputValue: ""
});
}
render() {
<form onSubmit={ this.handleSubmit }>
<input type="text"
placeholder="Controlled input"
onChange={ this.handleChange }
value={ this.state.inputValue } />
<button>Submit</button>
</form>
}
}
I have some Card (more than 10 Card component) in a Cards component and each Card has a form with more than 10 textField components. When I'm typing in textFields, It has delay between type and update value of textField. After spending more than 2 days, I found my problem. I think it's related to re-rendering all Childs (all Card components) when I set my value in statein value update... .
I want to know where am I wrong? If my codes is standard, is there any way to stop re-rendering all Childs after changing state for just one textField?
My codes are like as follow:
MainComponent:
export default class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
value : {}
};
}
static PropTypes = {
results: PropTypes.array.isRequired
};
handleChange(ref, e) {
this.state.value[ref] = e;
this.setState(this.state);
}
render() {
const { results } = this.props;
<Cards>
{
results.map((result, index) => {
var ref_taxtfield1 = result.id + "taxtfield1";
var ref_taxtfield2 = result.id + "taxtfield2";
var ref_taxtfield3 = result.id + "taxtfield3";
var ref_taxtfield4 = result.id + "taxtfield4";
var ref_taxtfield5 = result.id + "taxtfield5";
return <Card key={ result.id } style={ styles.card }>
<Form>
<div style={ styles.innerContainer }>
<Textfield
name="taxtfield1"
label="My Label 1"
ref={ref_taxtfield1}
onValueChange={this.handleChange.bind(this, ref_taxtfield1)}
value={this.state.value[ref_taxtfield1]}
/>
<Textfield
name="taxtfield2"
label="My Label 2"
ref={ref_taxtfield2}
onValueChange={this.handleChange.bind(this, ref_taxtfield2)}
value={this.state.value[ref_taxtfield2]}
/>
<Textfield
name="taxtfield3"
label="My Label 3"
ref={ref_taxtfield3}
onValueChange={this.handleChange.bind(this, ref_taxtfield3)}
value={this.state.value[ref_taxtfield3]}
/>
<Textfield
name="taxtfield4"
label="My Label 4"
ref={ref_taxtfield4}
onValueChange={this.handleChange.bind(this, ref_taxtfield4)}
value={this.state.value[ref_taxtfield4]}
/>
<Textfield
name="taxtfield5"
label="My Label 5"
ref={ref_taxtfield5}
onValueChange={this.handleChange.bind(this, ref_taxtfield5)}
value={this.state.value[ref_taxtfield5]}
/>
</div>
</Form>
</Card>})}
</Cards>
}
}
My TextField Component
export default class Textfield extends Input {
static defaultProps = {
initialCount: 0,
value: "",
defaultValue: "",
onValueChange: null,
label: ""
};
state = { focused: false };
onChange = this.onChange.bind(this);
onChange(e) {
if(this.props.onValueChange){
this.props.onValueChange(e.target.value);
}
}
handleOnBlur = this.handleOnBlur.bind(this);
handleOnBlur(e){
this.setState({focused: false});
if(this.props.onBlur){
this.props.onBlur(e);
}
}
render() {
const { focused } = this.state;
const { value, disabled } = this.props;
return (
<div>
<label>{this.props.label}</label>
<input
{ ...this.inputProps() }
type="text"
placeholder={this.props.placeholder}
defaultValue={this.props.defaultValue}
onChange={this.onChange}
onBlur={this.handleOnBlur}
value={ isCurrency ? formatData.currency(value) : value}
disabled={disabled}
/>
</div>
)
}
}
My Card and Cards Component
export class Cards extends Component {
render() {
const { children, ...props } = this.props;
return <div {...props} >{ children }</div>;
}
};
export class Card extends Component {
render() {
const { ...props } = this.props;
return <div {...props} } />
}
}
I use ES6 syntax and also remove all style tags from my code to simplify.
You are passing a new function to every Textfield component on render:
onValueChange={this.handleChange.bind(this, ref_taxtfield1)}
.bind returns a new function every time, causing every Textfield to render on each update.
Two possible solutions:
Don't use .bind inside .render. .bind the method once in the constructor and let Textfield pass an identifier to this.props.onValueChange.
Implement shouldComponentUpdate in Textfield, returning false if only this.props.onValueChange changed.