I have an input HTML tag, where the onChange is currently
onChange={() => { this.props.someFunc(this.props.someVal, e.target.checked) }
However, I want to follow the es-lint no-bind rule (I want to avoid inline functions), and I'm having issues hadling the arguments for this onChange function.
In my constructor I have:
constructor() {
super();
this.state = {
// some state
};
this._onChangeHandler = this._onChangeHandler.bind(this);
}
_this.onChangeHandler = (event, val) => {
this.props.someFunc(event.target.checked, val);
}
render() {
<div>
{
this.props.inputs.map((x) => {
const someValue = // ...a calculated value
return (
<label
}>
<input
onChange={ this._onChangeHandler(someValue) } // need BOTH someValue and the event
checked={ aBool }
type="checkbox"
value={ anotherValue }/>
<span>{ textHere }</span>
</label>
);
})
}
</div>
}
I've taken a look at this post, but no luck so far. What do I need to do to be able to pass both a value and the event to a bound function?
What if you use currying?
// Helper method that returns a function
const generateHandler = (value, method) => e => method(e, value)
// Apply the helper method
<input onChange={generateHandler(someValue, this._onChangeHandler)} />
You can try this:
<input
onChange={(e) => this._onChangeHandler(e, someValue)}
/>
From the es-lint example linked in Fleezey's comment. Here's what it would look like in your case:
var List = React.createClass({
constructor() {
super();
this._onChangeHandler = this._onChangeHandler.bind(this);
}
this._onChangeHandler = (event, val) => {
this.props.someFunc(event.target.checked, val);
}
render() {
<div>
{
this.props.inputs.map((x) => {
const someValue = // ...a calculated value
return (
<label>
<ListItem
onChange={ this._onChangeHandler }
changeHandlerValue={ someValue }
checked={ aBool }
value={ anotherValue } />
<span>{ textHere }</span>
</label>
);
})
}
</div>
}
});
var ListItem = React.createClass({
render() {
// render the input using the props passed in
return (
<input
onChange={this._onChange}
checked={this.props.checked}
type="checkbox"
value={this.props.value}
/>
);
},
_onChange(event) {
// trigger the event handler and pass both the event and the value
this.props.onChange(event, this.props.changeHandlerValue);
}
});
In the accepted currying solution above, the order of the arguments are wrong.
Plus it does not handle multiple args when the handler is actually invoked. Here's an improved version:
// Helper method that returns a function - order matters here!!!
const generateHandler = (value, method) => (...e) => method(value, ...e)
// Apply the helper method
<input onChange={generateHandler(someValue, this._onChangeHandler)} />
As you have your code at the moment, you receive in the event input variable the values of someValue and in the val input variable the event object. That said, you just need to invert the order of your two input variables so you receive what you expect.
When you bind functions to events, your input variables will be called first and then you will get whatever the API of the event is defined to return.
Related
I am trying to update the number of event from the parent component by using an input form from the child component, but there is something I am not seeing it either doesn't work or shows undefined
class App extends Component {
state = {
numberOfEvents: 32,
};
.....
updateNumberOfEvents = (eventNumber) => {
this.setState({ numberOfEvents: eventNumber });
};
render() {
return (
<div className="App">
<NumberOfEvents updateNumberOfEvents={this.updateNumberOfEvents} />
}
</div>
class NumberOfEvents extends Component {
state = {
numberOfEvents: 32,
};
handleInputChanged = (event) => {
const value = event.target.value;
this.setState({
numberOfEvents: value,
});
this.props.updateNumberOfEvents(value);
};
render() {
const numberOfEvents = this.state.numberOfEvents;
return (
<div className="numberOfEvents">
<form>
<label for="fname"> Number of Events:</label>
<input
type="text"
className="EventsNumber"
value={numberOfEvents}
onChange={this.handleInputChanged}
/>
</form>
</div>
);
}
}
export default NumberOfEvents;
this.setState({
numberOfEvents: value,
}, () => {
this.props.updateNumberOfEvents(value);
}
);
The details are here.
While this answer does make it work and correctly highlights that setState calls are asynchronous, I would suggest removing the local state inside NumberOfEvents entirely, as you currently have multiple sources of truth for your form.
Update your onChange handler:
handleInputChanged = (event) => {
this.props.updateNumberOfEvents(event.target.value);
};
and pass down the value from the parent:
<NumberOfEvents
updateNumberOfEvents={this.updateNumberOfEvents}
numberOfEvents={this.state.numberOfEvents}
/>
and use that value inside your child component:
<input
type="text"
className="EventsNumber"
value={this.props.numberOfEvents}
onChange={this.handleInputChanged}
/>
Having one source of truth is less error prone and easier to maintain, as illustrated by your current bug.
I agree with hotpink but i tried your code on codesandbox...it does not show the value undfined. Here is the link https://codesandbox.io/s/stackoverflow-iow19?file=/src/App.js.
Check console
For an example class component like below:
class Todo extends Component {
state = {
list: ["First Todo"],
text: ""
};
handleSubmit(e) {
e.preventDefault();
if (this && this.setState) {
console.log("this present in handleSubmit");
this.setState(prevState => ({
list: prevState.list.concat(this.state.text),
text: ""
}));
} else {
console.log("this not present in handleSubmit");
}
}
handleChange(e) {
if (this && this.setState) {
console.log("this present in handleChange");
this.setState({
text: e.target.value
});
} else {
console.log("this not present in handleChange");
}
}
removeItem(index) {
if (!this || !this.setState) {
console.log("this not present in removeItem");
}
console.log("this present in removeItem");
const list = this.state.list;
list.splice(index, 1);
this.setState({ list });
}
render() {
return (
<div>
<h1>TODO LIST</h1>
<form onSubmit={this.handleSubmit}>
<input value={this.state.text} onChange={e => this.handleChange(e)} />
<button>Add</button>
<ol>
{this.state.list.map((item, index) => {
return (
<li key={index}>
{item}
<button onClick={() => this.removeItem(index)}>Delete</button>
</li>
);
})}
</ol>
</form>
</div>
);
}
}
The behavior of this binding to the class methods is not consistent.
Playing around with the component we will find that, handleChange and removeItem has the correct this context, whereas handleSubmit has this context as undefined.
Both of the function which has correct this context is represented as an arrow function in jsx. Like below:
<input value={this.state.text} onChange={e => this.handleChange(e)} />
While the handleSubmit is passed as function itself. Like below:
<form onSubmit={this.handleSubmit}>
But, I really don't know why this is happening. Because, In my understanding, it should not have mattered how the function was passed i.e. as the function itself or arrow representation as above.
Arrow functions have lexical this. Which means its value is determined by the surrounding scope. So when you use it instead of class methods the this value will be maped to the instance. But when you call this.onSubmit this will be refering to the local scope and not to the instance itself. To solve it either use arrow functions or bind the onSubmit method in your constructor.
constructor(props){
super(props)
this.onSubmit = this.onSubmit.bind(this)
}
In my understanding, it should not have mattered how the function was passed...
So here is new thing to learn
Passing onChange={e => this.handleChange(e)} will be the same as using .bind in the constructor or passing the reference and using .bind.
When passing it as an arrow function in the render method, it will get the this of the component instead of the method's this.
You should notice that onChange={e => this.handleChange(e)} is not a good practice because on every render you will be creating a new function.
I'm using React with TypeScript, and I've created a Input Component which will take lots of Props, and many of them are optional. So far I've this.
interface InputProps {
labelClassName?: string;
labelName?: string;
inputType: string;
inputName?: string;
inputValue: any;
inputOnChange?: (e: any) => void;
readOnly?: boolean;
}
Now, in the Input Component I want to render an label tag with an input inside. This input will inherit basically all the props, so I will have something like this.
export class Input extends React.Component<InputProps, {}> {
render() {
console.log(this.props);
return (
<label className={this.props.labelClassName}>
{this.props.labelName}
<input
type={this.props.inputType}
name={this.props.inputName}
value={this.props.inputValue || ""}
readOnly={this.props.readOnly}
onChange={(e) => this.props.inputOnChange(e)} />
</label>)
;
}
}
Now, the problem is that I can't write the line with OnChange because TypeScript tell me "Object is possibly 'undefined'", which is totally true, since this.props.inputOnChange it's an optional props.
So, what I'd like to write it's something like "if this.props.inputOnChange(e) != undefined, then add onChange in the input", but... I don't know how do this.
I've tried the conditional rendering:
{this.props.inputOnChange &&
onChange = {(e) => this.props.inputOnChange(e)} />
But it does not work
EDIT: One solution I did found it's to write something like this.
let inputOnChange = (this.props.inputOnChange != undefined)
? this.props.inputOnChange
: undefined;
...
<input
...
onChange={inputOnchange} />
But I honestly don't know if it's ok to pass an undefined to onChange
Generally you will want to handle onChange by providing your own function, and then conditionally propagating if the prop exists. Something like this:
export class Input extends React.Component<InputProps, {}> {
handleOnChange = (e) => {
if (this.props.inputOnChange) {
this.props.inputOnChange(e);
}
}
render() {
console.log(this.props);
const { labelClassName, labelName, inputType, inputName,
inputValue, readOnly } = this.props;
return (
<label className={labelClassName}>
{labelName}
<input
type={inputType}
name={inputName}
value={inputValue || ""}
readOnly={readOnly}
onChange={this.handleOnChange} />
</label>
);
}
}
I'll give you one extra piece of advice, don't ever create an arrow function inside your render method if you can help it. For example:
<input onChange={(e) => something} />
The above creates a new function each time the render method is called. This will cause react to re-render the entire sub component tree because the function reference changed. In this case, it might not be a big deal, but if you had a large subtree of components you can run into performance issues relatively quickly.
You can include conditional props like this.
<input
{...this.props.inputOnChange && { onChange: this.props.inputOnChange }}
// or
{...this.props.inputOnChange && { onChange: (e) => this.props.inputOnChange(e, 'foo') }}
/>
Repeat for as many props as you need. This is just using the Spread Attributes syntax and you are not limited to just one spread or just one property in the object.
Meaning, this is all ok:
<input
{...props} // you already know this spread syntax
{...props && props} // spreads if defined
{...a && { a }} // shorthand property name
{...c && { c: (e) => c(e) }}
{...x && { x, y:2, z:3 }} // multiple properties
/>
Shorthand property names (ES2015)
You can define conditionalProps object
let conditionalProps = {}
if (this.props.inputOnChange)
conditionalProps["onChange"] = this.props.inputOnChange
// or with custom fn
if (this.props.inputOnChange)
conditionalProps["onChange"] = (ev) => this.props.inputOnChange(ev, otherArgs)
return <input { ...conditionalProps} />
Alternatively
return (
<XyzSomeOtherJSXMarkup>
{this.props.inputOnChange
? <input onChange={this.props.inputOnchange} />
: <input />}
</XyzSomeOtherJSXMarkup>
)
I've been using React for a few months now, and one of the things I'm finding most difficult is how to properly bind functions that take arguments.
Currently, I have three inputs that share a single update function, but require a different first argument to be passed. Here is my component:
class MyComponent extends Component {
render() {
const { onChange } = this.props;
return(
<div className='my_component'>
<Row>
<Column>
<Input
value={item1}
onChange={ (newValue) => onChange('item1', newValue) } />
</Column>
</Row>
<Row>
<Column>
<Input
value={item2}
onChange={ (newValue) => onChange('item2', newValue) } />
</Column>
</Row>
<Row>
<Column>
<Input
value={item3}
onChange={ (newValue) => onChange('item3', newValue) } />
</Column>
</Row>
</div>
);
}
}
So, currently, I'm using arrow functions in my render function of the component. But through research, I've found that obviously has performance issue in terms of re-rendering.
The solution offered is to bind in the constructor using
constructor() {
super();
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.props.onChange('ARGUMENT REQUIRED!', event.target.value);
}
The problem is, that I cannot get that first argument to work... Am I supposed to create a function for each and bind one for each in the constructor, like so:
constructor() {
super();
this.handleItem1Change= this.handleItem1Change.bind(this)
this.handleItem2Change= this.handleItem2Change.bind(this)
this.handleItem3Change= this.handleItem3Change.bind(this)
}
handleItem1Change(newValue) {
this.props.onChange('item1', newValue);
}
handleItem2Change(event) {
this.props.onChange('item2', newValue);
}
handleItem3Change(event) {
this.props.onChange('item3', newValue);
}
That seems repetitive...is there a more streamlined way to do this?
If you have control of the Input component, why not have a prop such as name and then in the Input component pass in the onChange function as a prop.
In the Input component whereever you are handling the change you could just do.
<Input
value={item3}
onChange={ onChange }
name='item3'
/>
// in the Input component
handleChange = (value) => {
this.props.onChange(value, this.props.name)
}
and then you would just need to update your onChange to put the value first and the name second. Reason for doing it that way is to ensure this doesn't break your Input component in the other places that it is used since value will still be the first argument, and name is a secondary optional argument.
if you are passing the event back in the onChange instead of value you can still use the event and just do e.target.name as long as you are applying the name prop to the input thats rendered in Input and would look like:
handleChange(event) {
this.props.onChange(event.target.name, event.target.value);
}
You can pass arguments that you want to partially apply right into bind:
constructor() {
super();
this.handleItem1Change = this.props.onChange.bind(this, 'item1');
this.handleItem2Change = this.props.onChange.bind(this, 'item2');
this.handleItem3Change = this.props.onChange.bind(this, 'item3');
}
Alternatively, you can still use arrow functions there:
constructor() {
super();
this.handleItem1Change = newValue => this.props.onChange('item1', newValue);
this.handleItem2Change = newValue => this.props.onChange('item2', newValue);
this.handleItem3Change = newValue => this.props.onChange('item3', newValue);
}
Just add a new layer of abstraction, something like this::
form.js //use the component RowInput
<RowInput name={'item1'} value={'item'} onChange={onChange} />
RowInput.js // stupid/dumb component
export const RowInput = (name, item, onChange)=>
<Column>
<Input
value={item}
onChange={ (val) => onChange(name) } />
</Column>
you could try this to bind the on change callback
<Input value={item1} onChange={onChange.bind(null,'item1') } />
the callback will look like this
onChange(item, event)
My target is to call multiple functions onClick. Assume that some of them has taken event and some value, so it is unable to call them in single function. How can I achieve that?
render() {
//describe first function (1)
const amountSelectClearInput = function(event) {
// call two functions here
this.amountSelect(event);
this.clearInput();
}.bind(this);
var self = this;
return (
<div>
<div>
{amounts.map(function (name, index) {
return <input type="button" value={name}
className={self.state.active === name ? 'active' : ''}
//here I must call () => self.changeClass(name) (1), and amountSelectClearInput (2)
onClick={() => self.changeClass(name)} key={ name }/>;
})}
</div>
</div>
);
}
I need to call amountSelectClearInput and () => self.changeClass(name) at same time
onClick={function() { amountSelectClearInput(event); self.changeClass(name)}}