When we use the builtin HTML input components, the onChange listener is given an instance of SyntheticEvent, which allows us to do things like
onChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
};
render() {
return (
<div>
<input
type="text"
name="username"
value={ this.state.username }
onChange={ this.onChange } />
<input
type="password"
name="password"
value={ this.state.password }
onChange={ this.onChange } />
</div>
);
}
Notice that I can use the same listener for multiple form controls. However, if we use a custom form control, say, a DatePicker or something more domain-specific like UserSelector, these components usually have their own API to handle changes (e.g., onChange(newValue) or onChange(oldValue, newValue)). This forces me to write one listener per field.
Is there any good way to deal with this situation?
One approach would be to construct your own data structure to emit that is similar to an event.
import React, { PureComponent } from 'react';
const onChange = component => event => {
component.props.onChange({
target: {
value: Object.assign({}, component.props.value, {
[event.target.name]: event.target.value,
})
}
})
};
export default class UserControl extends PureComponent {
render () {
return (
<div>
<input name="name" value={this.props.value.name} onChange={onChange(this)} />
<input name="age" value={this.props.value.age} onChange={onChange(this)} />
</div>
);
}
};
Your custom event object can pluck other keys of React's synthetic event as required.
Note that the onChange handler doesn't have to be tied to a class. Since it's generic, it can exist as a separate bit of code that accepts a component first.
EDIT: One thing to consider is to also pass along the stopPropagation method in case it's needed. I haven't had to do this myself yet, but this seems to work. Note the event.persist(). This ensures the SyntheticEvent is not reused by React.
const onChange = component => event => {
event.persist();
component.props.onChange({
stopPropagation: event.stopPropagation.bind(event),
target: {
value: Object.assign({}, component.props.value, {
[event.target.name]: event.target.value
})
}
});
};
Related
Below is my code for a personal project where i can keep track of my monthly subscriptions, if i have to add a subscription i just have a add an object to an existing array. however for testing purposes when i tried to console.log(value.startDate) in handleSubmit it gives me undefined and causes further problems. How would i fix it?
import React from 'react';
import PropTypes from 'prop-types';
const List = () => {
const [ mylist, setList ] = React.useState([]);
const [ value, setValue ] = React.useState({ subscription: '', startDate: '', paymentTime: 0 });
const handleSubmit = (e) => {
console.log(value.startDate);
setList(mylist.push(value));
e.preventDefault();
};
const handleOnChange = (event) => {
setValue({ [event.target.name]: event.target.value });
};
return (
<div>
<div className="for_list">
<ul className="list">{mylist.map((obj) => <li key={obj.subscription}>{obj.subscription}</li>)}</ul>
</div>
<div className="for_form">
<form>
<input type="text" name="subscription" onChange={handleOnChange} value={value.subscription} />
<input type="text" name="startDate" onChange={handleOnChange} value={value.startDate} />
<input type="number" name="paymentTime" onChange={handleOnChange} value={value.paymentTime} />
</form>
</div>
<button onClick={handleSubmit}>Add Item</button>
</div>
);
};
// it just removes the error above.
List.propTypes = {
list: PropTypes.node
};
export default List;
You are replacing your state every time. This might be because of the miss in understanding the difference between setState in traditional class based React components and useState.
You need to append the value to the existing data. Something similar would work
const handleOnChange = (event) => {
setValue({ ...value, [event.target.name]: event.target.value });
};
The setState in class based components always accepts partial state and merges with the existing one. While useState setter function replaces the value you provide in the respective state.
On handleChange function you need to pass the old value of value
const handleOnChange = (event) => {
setValue({ ...value , [event.target.name]: event.target.value });
};
Solved see comment below (Only had to move the Chooser and Section function outside of the class component to get it to work.
So I have a problem with the react onChange function. It does not seem to work when it is passed to a component as props. I tried to pass the component instead of the data but still it did not work. Please consider the following example:
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
type: '1',
number: ''
}
}
handleChange = e => {
const { name, value } = e.target
this.setState({ [name]: value })
}
render() {
return (
<div className="App">
<Form
type={this.state.type}
number={this.state.number}
handleChange={this.handleChange}
/>
</div>
}
}
//receiving the props
export default class Child extends Component {
constructor(props) {
super(props)
}
render() {
const Chooser = ({ type, section }) => {
switch (type) {
case '1':
return <Fragment>{section}</Fragment>
default:
return <Fragment>></Fragment>
}
}
const Section = ({ number, handleChange }) => (
<Fragment>
<div>
<label>Number</label>
<input
type='text'
name='number'
placeholder='123456789'
value={number}
onChange={handleChange}
/>
</div>
</Fragment>
)
return (
<Chooser
type={this.props.type}
section={
<Section
number={this.props.number}
handleChange={this.props.handleChange}
/>
}
/>
)
}
}
Interestingly if I put the onChange on the Section level it does work. But this is not what I want since a passed component could have multiple Input functions that I want to pass.
return (
<Chooser
type={this.props.type}
section={
<Section
number={this.pops.number}
onChange={this.pops.handleChange}
/>
}
/>
Any ideas how I can pass the onChange function down using props? On a similar example the Input change does work but it is loosing focus each time a value is pressed. Already tried assigning keys but that did not work either.
You currently have this as your handleChange method
handleChange = e => {
const { name, value } = e.target.value
this.setState({ [name]: value })
}
You should change it to this.
handleChange = e => {
const { name, value } = e.target;
this.setState({ [name]: value })
}
You seem to be accessing the wrong property in the target because name will always be undefined inside e.target.value and as such, calling setState won't do anything.
Also, you should probably be declaring your function components outside of the class component.
this is because you need to destructure it like this on the next like
const {handleChange} = this.props.handleChange
You can wrap handleChange() in an anonymous function so that it will actively wait for you to make changes to the input. Otherwise it will run on render.
const Section = ({ number, handleChange }) => (
<Fragment>
<div>
<label>Number</label>
<input
type='text'
name='number'
placeholder='123456789'
value={number}
onChange={(e) => handleChange(e)}
/>
</div>
</Fragment>
)
With the below code
<div className="input-first-line-right">
<Input
type="textarea"
label="Project Description"
onChange={this.handleDescChange}
value={this.state.projectDescription}
/>
</div>
handleDescChange = text => {
if (!text) return;
this.setState({ projectDescription: text });
};
If handleDescChange is expecting an argument 'text' how come it is never passed.
Meaning why isn't the code
onChange={this.handleDescChange("some new text")}
inorder for the function to work. How does the code inherintly know what the parameter is if nothing is ever passed to it.
For onChange attribute, this.handleDescChange isn't called here.
Here, this.handleDescChange is given as callback. Input component calls this.handleDescChange when the change event is triggered.
If you want to pass a variable you can use fat arrow function. Solution is given below.
<div className="input-first-line-right">
<Input
type="textarea"
label="Project Description"
onChange={event => this.handleDescChange(event.target.value)}
value={this.state.projectDescription}
/>
</div>
handleDescChange = text => {
if (!text) return;
this.setState({ projectDescription: text });
};
This warning is triggered when we try to access to a React synthetic event in an asynchronous way. Because of the reuse, after the event callback has been invoked the synthetic event object will no longer exist so we cannot access its properties. source
The source link above have the correct answer but here is the summary:
Use event.persist()
Note: 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. React documentation
class App extends Component {
constructor(props) {
super(props);
this.state = {
projectDescription: ""
};
}
handleDescChange = event => {
// Add here
event.persist();
// Don't check for '!text' Check if there is a value
if (event.target.value === 0) return;
this.setState({ projectDescription: event.target.value });
};
render() {
return (
<div className="input-first-line-right">
<input
type="textarea"
label="Project Description"
onChange={this.handleDescChange}
value={this.state.projectDescription}
/>
</div>
);
}
}
Though, I would recommend to start learning/use React hooks as it is more clean, elegant way to do this:
import React, { useState } from "react";
const App = () => {
// useState hook
const [projectDescription, setProjectDescription] = useState("");
const handleDescChange = event => {
if (event.target.value === 0) return;
setProjectDescription(event.target.value);
};
return (
<div className="input-first-line-right">
<input
type="textarea"
label="Project Description"
onChange={handleDescChange}
value={projectDescription}
/>
</div>
);
};
I am looking to create a stateless component who's input element can be validated by the parent component.
In my example below, I am running into a problem where the input ref is never being assigned to the parent's private _emailAddress property.
When handleSubmit is called, this._emailAddress is undefined. Is there something I'm missing, or is there a better way to do this?
interface FormTestState {
errors: string;
}
class FormTest extends React.Component<void, FormTestState> {
componentWillMount() {
this.setState({ errors: '' });
}
render(): JSX.Element {
return (
<main role='main' className='about_us'>
<form onSubmit={this._handleSubmit.bind(this)}>
<TextInput
label='email'
inputName='txtInput'
ariaLabel='email'
validation={this.state.errors}
ref={r => this._emailAddress = r}
/>
<button type='submit'>submit</button>
</form>
</main>
);
}
private _emailAddress: HTMLInputElement;
private _handleSubmit(event: Event): void {
event.preventDefault();
// this._emailAddress is undefined
if (!Validators.isEmail(this._emailAddress.value)) {
this.setState({ errors: 'Please enter an email address.' });
} else {
this.setState({ errors: 'All Good.' });
}
}
}
const TextInput = ({ label, inputName, ariaLabel, validation, ref }: { label: string; inputName: string; ariaLabel: string; validation?: string; ref: (ref: HTMLInputElement) => void }) => (
<div>
<label htmlFor='txt_register_first_name'>
{ label }
</label>
<input type='text' id={inputName} name={inputName} className='input ' aria-label={ariaLabel} ref={ref} />
<div className='input_validation'>
<span>{validation}</span>
</div>
</div>
);
You can useuseRef hook which is available since v16.7.0-alpha.
EDIT: You're encouraged to use Hooks in production as of 16.8.0 release!
Hooks enable you to maintain state and handle side effects in functional components.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Read more in Hooks API documentation
EDIT: You now can with React Hooks. See the answer by Ante Gulin.
You can't access React like methods (like componentDidMount, componentWillReceiveProps, etc) on stateless components, including refs. Checkout this discussion on GH for the full convo.
The idea of stateless is that there isn't an instance created for it (state). As such, you can't attach a ref, since there's no state to attach the ref to.
Your best bet would be to pass in a callback for when the component changes and then assign that text to the parent's state.
Or, you can forego the stateless component altogether and use an normal class component.
From the docs...
You may not use the ref attribute on functional components because they don't have instances. You can, however, use the ref attribute inside the render function of a functional component.
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
This is late but I found this solution much better.
Pay attention to how it uses useRef & how properties are available under current property.
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
For more reference check react docs
The value of your TextInput is nothing more than a state of your component. So instead of fetching the current value with a reference (bad idea in general, as far as I know) you could fetch the current state.
In a reduced version (without typing):
class Form extends React.Component {
constructor() {
this.state = { _emailAddress: '' };
this.updateEmailAddress = this.updateEmailAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
updateEmailAddress(e) {
this.setState({ _emailAddress: e.target.value });
}
handleSubmit() {
console.log(this.state._emailAddress);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
value={this.state._emailAddress}
onChange={this.updateEmailAddress}
/>
</form>
);
}
}
You can also get refs into functional components with a little plumbing
import React, { useEffect, useRef } from 'react';
// Main functional, complex component
const Canvas = (props) => {
const canvasRef = useRef(null);
// Canvas State
const [canvasState, setCanvasState] = useState({
stage: null,
layer: null,
context: null,
canvas: null,
image: null
});
useEffect(() => {
canvasRef.current = canvasState;
props.getRef(canvasRef);
}, [canvasState]);
// Initialize canvas
useEffect(() => {
setupCanvas();
}, []);
// ... I'm using this for a Konva canvas with external controls ...
return (<div>...</div>);
}
// Toolbar which can do things to the canvas
const Toolbar = (props) => {
console.log("Toolbar", props.canvasRef)
// ...
}
// Parent which collects the ref from Canvas and passes to Toolbar
const CanvasView = (props) => {
const canvasRef = useRef(null);
return (
<Toolbar canvasRef={canvasRef} />
<Canvas getRef={ ref => canvasRef.current = ref.current } />
}
Currently in react js, when I want to bind a text area or an input with a "state", I will need to set the onChange method and setState() everytime user type in a single letter
I heard if you setState react js refresh and re-render everything in this component
Is there any more efficient way to do so? using "shouldComponentUpdate" will be improper in this case since if I don't make "state" update, all user input will be stuck..
Well, that's how you implement controlled input elements in React.
However, if performance is a major concern of yours, you could either isolate your input element in a separate stateful component, hence only triggering a re-render on itself and not on your entire app.
So something like:
class App extends Component {
render() {
return (
<div>
...
<MyInput />
...
</div>
);
}
}
class MyInput extends Component {
constructor() {
super();
this.state = {value: ""};
}
update = (e) => {
this.setState({value: e.target.value});
}
render() {
return (
<input onChange={this.update} value={this.state.value} />
);
}
}
Alternatively, you could just use an uncontrolled input element. For example:
class App extends Component {
render() {
return (
<div>
...
<input defaultValue="" />
...
</div>
);
}
}
Though, note that controlled inputs are generally recommended.
As #Chris stated, you should create another component to optimize the rerendering to only the specified component.
However, there are usecases where you need to update the parent component or dispatch an action with the value entered in your input to one of your reducers.
For example I created a SearchInput component which updates itself for every character entered in the input but only call the onChange function only if there are 3 characters at least.
Note: The clearTimeout is useful in order to call the onChange function only when the user has stopped typing for at least 200ms.
import React from 'react';
class SearchInput extends React.Component {
constructor(props) {
super(props);
this.tabTimeoutId = [];
this.state = {
value: this.props.value,
};
this.onChangeSearch = this.onChangeSearch.bind(this);
}
componentWillUpdate() {
// If the timoutId exists, it means a timeout is being launch
if (this.tabTimeoutId.length > 1) {
clearTimeout(this.tabTimeoutId[this.tabTimeoutId.length - 2]);
}
}
onChangeSearch(event) {
const { value } = event.target;
this.setState({
value,
});
const timeoutId = setTimeout(() => {
value.length >= this.props.minSearchLength ? this.props.onChange(value) : this.props.resetSearch();
this.tabTimeoutId = [];
}, this.props.searchDelay);
this.tabTimeoutId.push(timeoutId);
}
render() {
const {
onChange,
minSearchLength,
searchDelay,
...otherProps,
} = this.props;
return <input
{...otherProps}
value={this.state.value}
onChange={event => this.onChangeSearch(event)}
/>
}
}
SearchInput.propTypes = {
minSearchLength: React.PropTypes.number,
searchDelay: React.PropTypes.number,
};
SearchInput.defaultProps = {
minSearchLength: 3,
searchDelay: 200,
};
export default SearchInput;
Hope it helps.
You need to bind the onChange() event function inside constructor like as code snippets :
class MyComponent extends Component {
constructor() {
super();
this.state = {value: ""};
this.onChange = this.onChange.bind(this)
}
onChange= (e)=>{
const formthis = this;
let {name, value} = e.target;
formthis.setState({
[name]: value
});
}
render() {
return (
<div>
<form>
<input type="text" name="name" onChange={this.onChange} />
<input type="text" name="email" onChange={this.onChange} />
<input type="text" name="phone" onChange={this.onChange} />
<input type="submit" name="submit" value="Submit" />
</form>
</div>
);
}
}
You don't need a complicated react solution to this problem, just a little common sense about when to update state. The best way to achieve this is to encapsulate your setState call within a timeout.
class Element extends React.Component {
onChange = (e) => {
clearTimeout(this.setStateTimeout)
this.setStateTimeout = setTimeout(()=> {
this.setState({inputValue: e.target.value})
}, 500)
}
}
This will only set state on your react element a 500ms after the last keystroke and will prevent hammering the element with rerenders as your user is typing.