Input Attribute onChange only if Props is not Undefined - javascript

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>
)

Related

How to define React component with conditional props interface?

i need to define "Field" component that renders textarea or input depends on prop multiline
I trying to do this like that:
import React from 'react';
type Props<T extends boolean = boolean> = { multiline: T } & T extends true
? React.HTMLProps<HTMLTextAreaElement>
: React.HTMLProps<HTMLInputElement>
export const Field: React.FC<Props> = ({ multiline, ...props }) => { // error here
const Element = multiline ? 'textarea' : 'input';
return <Element {...props} onInput={e => {}} />; // error here
}
// usage
const result = (
<Field onChange={e => console.log(e.target.value)} /> // error here
);
But typescript provide several errors like:
1 Property 'multiline' does not exist on type 'HTMLProps<HTMLInputElement> & { children?: ReactNode; }'.(2339)
2 [large error, more in playground]
3 Property 'value' does not exist on type 'EventTarget'.(2339)
Playground here
How can i define such component?
Problem: No T in Field
You have defined a generic type Props that depends on T but your component is not generic. It always takes Props<boolean> which resolves to the HTMLInputElement props because boolean extends true is false. The reason {multiline: boolean} is getting lost is because you need parentheses around the rest of your type.
React.HTMLProps
When using your React.HTMLProps typings I didn't get errors when assigning mismatched properties like type="number" to a textarea or rows={5} to an input. The more restrictive types are JSX.IntrinsicElements['textarea'] and JSX.IntrinsicElements['input'] (which resolve to a type like React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>). If you want strict enforcement then use these! This also makes the e value in the onChange callback get the correct type based on the element.
Implementation
When using a generic component with restrictive types, we now get an error in the implementation on return <Element {...props} />; I thought that breaking it into two (return multiline ? <textarea {...props} /> : <input {...props}/>;) would help but we still get errors. Conditionals are rough. You can use as assertions to fix things. I'm generally ok with making assertions in the implementation of a function when the usage of it stays strictly typed. So you can do this:
type Props<T extends boolean = boolean> = { multiline: T } & (T extends true
? JSX.IntrinsicElements['textarea']
: JSX.IntrinsicElements['input'])
export const Field = <T extends boolean>({ multiline, ...props }: Props<T>) => {
const Element = multiline ? 'textarea' : 'input';
return <Element {...props as any} />;
}
Playground #1
Union Type
We can avoid having to make assertions by typing Props as a union of two situations. This allows us to check which type in the union we have by looking at props.multiline. This gets messy though because you cannot desctructure until after you have discriminated the union, but we don't want to pass multiline through to the DOM.
This code passes all type checks, but it needs additional work to prevent passing multiline through to the DOM.
type Props = (
{ multiline: true } & JSX.IntrinsicElements['textarea'] |
{ multiline: false } & JSX.IntrinsicElements['input']
);
export const Field = ({ ...props }: Props) => {
return props.multiline ? <textarea {...props} /> : <input {...props}/>
}
Playground #2
Usage
Either way the usage is very strongly typed! Our onChange callback gets the correct type like React.ChangeEvent<HTMLTextAreaElement> and we get error if passing textarea props when multiline={false} or vice-versa.
<Field
onChange={e => console.log(e.target.value)} // e: React.ChangeEvent<HTMLTextAreaElement>
multiline={true}
rows={5} // ok
type="number" // error
/>
<Field
onChange={e => console.log(e.target.value)} // e: React.ChangeEvent<HTMLInputElement>
multiline={false}
type="number" // ok
rows={5} // error
/>
I believe you should be using an intersection of the 2 types, so it can be either/or. So for Props type use the '&' operator to cover both input types, then in the onChange event use it again for each input type event (since it can be either). Something like this:
import React from 'react';
type Props<T extends boolean = boolean> = { multiline?: T } &
React.HTMLProps<HTMLTextAreaElement> &
React.HTMLProps<HTMLInputElement>;
export const Field: React.FC<Props> = ({ multiline, ...props }) => {
const Element = multiline ? 'textarea' : 'input';
return <Element {...props} />;
}
// usage
const result = (
<Field onChange={(e: React.ChangeEvent<HTMLInputElement> & React.ChangeEvent<HTMLTextAreaElement>) => (
console.log(e.target.value)
)} />
);

Get containing component from nested component

I am writing a ControlledInput component, and in order to have access to the state of the component using ControlledInput, I have a binder prop in ControlledInput.
I'm having a slight issue when using the component:
render() {
const CI = props => <ControlledInput binder={this} {...props} />;
return (
<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput binder={this} label={"Title"} />
</div>
);
}
The implementation above works completely fine. However, note the const CI I've defined. I tried to use this so I could just write <CI label={"Title"}/> without the binder since the binder will be the same on all the ControlledInput components I use in a given render method.
The problem with using <CI label={"Title"}/> is that when I type into the input, the input "blurs" and I have to reselect it. This appears to be because the render method creates the CI on every render.
I hope I've explained that clearly, because my head hurts.
Anyway, it makes sense to me why this happens. And I know that one solution is to put const CI = props => <ControlledInput binder={this} {...props} />; outside of the render function. But then I'd have to call it as <this.CI> and that starts to defeat the purpose.
And I can't put CI in global scope because then I don't have access to this.
Is there a way to solve this?
Update
Here is the current (very much in progress) code for ControlledInput:
// #flow
import React, { Component } from "react";
type Props = {
containerStyle?: Object,
label: string,
propName?: string,
binder: Component<Object, Object>,
onChange?: Object => void
};
class ControlledInput extends Component<Props> {
render() {
const props = this.props;
const propName = props.propName || props.label.toLowerCase();
return (
<div style={props.containerStyle}>
<p>{props.label}</p>
<input
type="text"
label={props.label}
onChange={
this.props.onChange ||
(e => {
props.binder.setState({ [propName]: e.target.value });
})
}
value={props.binder.state[propName]}
></input>
</div>
);
}
}
The point of this whole endeavor is to simplify creating a form with controlled components, avoiding having to add value={this.state.whatever} and onChange={e=>this.setState({whatever: e})} to each one, which is not DRY in my opinion.
And then I want get a little more DRY by not passing binder={this} to every component and that's why I'm doing const CI = props => <ControlledInput binder={this} {...props} />;, which, again, has to be inside the class to access this and inside the render function to be called as CI rather than this.CI.
So that first explanation why you need to pass this, although I suppose I could also have props like setState={this.setState} parentState={this.state}, and in that case it does indeed start to make sense to combine those into something like {...propsToSend} as #John Ruddell suggested.
Note that I've provided a possibility to override onChange, and plan on doing so for most or all of the other props (e.g, value={this.props.value || binder.state[propName]}. If one were to override a lot of these (especially value and onChange) it would indeed make the component much less reusable, but the main use case is for quickly creating multiple inputs that don't have special input handling.
So, again, my ideal would be to call <ControlledInput label="Title"/> and have the component code take care of binding state and setState correctly. If this is possible. And then the second option would be to have a place to define the necessary context props in a place that makes it simple when it's time to actually use the component multiple times, like so:
<ControlledInput label={"title"} {...contextProps}/>
<ControlledInput label={"author"} {...contextProps}/>
<ControlledInput label={"email"} {...contextProps}/>
<ControlledInput label={"content"} textArea={true} {...contextProps}/> // textarea prop not implemented yet, fyi
etc
I hear that accessing the parent state/context may be an anti-pattern, but there must be some way to do what I'm trying to do without using an anti-pattern, isn't there?
If you want the state of the parent, handle the state there and pass down the value to your input - ControlledInput won't have to know anything except how to handle data in and out. Something like this, and note that I jacked up the names a little so you can see which component is handling what:
import React, { useState } from "react"
const Parent = () => {
const [title, setTitle] = useState("")
const handleChangeInParent = (newTitle) => {
setTitle((oldValue) => newTitle)
}
return(<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput handleChange={handleChangeInParent} label={title} />
</div>)
}
const ControlledInput = ({handleChange, label}) => {
return (
<input onChange={handleChange} type="text" value={label} />
)
}
If ControlledComponent needs to handle its own state, then pass it a default value and then have the Parent read the value when saving (or whatever):
import React, { useState } from "react"
const Parent = () => {
const handleSaveInParent = (newTitle) => {
console.log("got the new title!")
}
return (
<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput handleSave={handleSaveInParent} initialLabel="Title" />
</div>
)
}
const ControlledInput = ({ handleSave, initialLabel }) => {
const [title, setTitle] = useState(initialLabel)
const handleChange = (ev) => {
const value = ev.target.value
setTitle((oldValue) => value)
}
const handleSubmit = (ev) => {
ev.preventDefault()
handleSave(title)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} type="text" value={title} />
</form>
)
}
You shouldn't be sending this through - just send values and/or functions to handle values.
With Updated Implementation
(okay, John you win!)
Not positive if this is technically an "answer", but I've rewritten the component to take a state and (updated) a setterFn prop:
component
// #flow
import React, { Component } from "react";
type Props = {
containerStyle?: Object,
labelStyle?: Object,
label: string,
propName?: string,
state: Object,
onChange?: Object => void,
textArea?: boolean,
setterFn: (key: string, value: mixed) => void
};
class ControlledInput extends Component<Props> {
render() {
const props = this.props;
const propertyName = props.propName || props.label.toLowerCase();
const TagType = props.textArea ? "textarea" : "input";
// only pass valid props to DOM element (remove any problematic custom props)
const { setterFn, propName, textArea, ...domProps } = props;
return (
<div style={props.containerStyle}>
<p style={props.labelStyle}>{props.label}</p>
<TagType
{...domProps}
label={props.label} // actually could get passed automatically, but it's important so I'm leaving it in the code
onChange={
this.props.onChange ||
(setterFn ? e => setterFn(propertyName, e.target.value) : null)
}
value={props.state[propertyName] || ""}
></TagType>
</div>
);
}
}
export default ControlledInput;
in use (somehow less code than before!)
class Wrapper extends Component<Object, Object> {
state = {};
render() {
const setterFn = (k, v) => this.setState({ [k]: v });
const p = { state: this.state, setterFn: setterFn.bind(this) };
return <ControlledInput {...p} {...this.props.inputProps} />
}
}
I guess this is more appropriate. It still takes up a lot more space than binder={this}.
It doesn't actually the questions of:
How to access the parent's state from the component. Though from comments it seems like this is an anti-pattern, which I do understand from the theory of React.
How to set these repeating props elsewhere so that I can just call `. I guess the only solution is to do something like this:
render() {
const props = {state: this.state, setState: this.setState}
<ControlledInput {...props} label="Title"/>
}
Which certainly isn't such a bad solution. Especially if I shorten that name to, say, a single character.
Much thanks to #John Ruddell for setting me on the right path.

How to avoid adding field change handling methods and other boilerplates in react form?

I have a react based form with more than 10 fields in it, I have states for those ten form fields (controlled component).
Most of these input fields are of type text only but other types fields will be added later.
Problem is that I need to write 10 change handlers for them to set state correctly and then add method bindings for each of them in constructor.
I am quite new to react and may be not aware about correct
methodologies and techniques.
Please guide me to how to improve my current code structure and avoid writing boiler plates and repetitive error prone code.
My current Registration component is like below -
export default class Register extends Component {
constructor(props){
super(props);
this.state = {
regName : '',
regAdd1 : '',
regAdd2 : '',
regState : '',
regZipCode : '',
regCity : '',
regPhone : ''
};
// add bindings .... ugh..
this.changeRegAdd1 = this.changeRegAdd1.bind(this);
this.changeRegAdd2 = this.changeRegAdd2.bind(this);
//Similary binding for other handlers...
}
// add individual field change handlers ... ugh...
changeRegName(e) {
this.setState({regName:e.target.value});
}
changeRegAdd1(e) {
this.setState({regAdd1:e.target.value});
}
changeRegAdd2(e) {
this.setState({regAdd2:e.target.value});
}
changeRegState(e) {
this.setState({regState:e.target.value});
}
// Similary for other change handler ....
handleSubmit(e) {
e.preventDefault();
// validate then do other stuff
}
render(){
let registrationComp = (
<div className="row">
<div className="col-md-12">
<h3>Registration Form</h3>
<fieldset>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regName">Name</label>
<input type="text" placeholder="Name"
onChange={this.changeregName} value = {this.state.regName} className="form-control" required autofocus/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regAdd1">Address Line1</label>
<input
type = "text"
placeholder = "Address Line1"
onChange = {this.changeregAdd1}
value = {this.state.regAdd1}
className = "form-control"
required
autofocus
/>
<input
type = "text"
placeholder = "Address Line2"
onChange = {this.changeregAdd2}
value = {this.state.regAdd2}
className = "form-control"
required
autofocus
/>
</div>
</div>
<div className="form-group">
<div className="col-xs-6">
<label htmlFor="regState">State</label>
<input
type = "text"
placeholder = "State"
onChange = {this.changeregState}
value = {this.state.regState}
className = "form-control"
required
autofocus
/>
</div>
<div className="col-xs-6">
<label htmlFor="regZipCode">Zip Code</label>
<input
type = "text"
placeholder = "Zip Code"
onChange = {this.changeregZipCode}
value = {this.state.regZipCode}
className = "form-control"
required
autofocus
/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regCity">City</label>
<input
type = "text"
placeholder = "City"
title = "City"
onChange = {this.changeregCity}
value = {this.state.regCity}
className = "form-control"
required
autofocus
/>
</div>
</div>
{/* other form fields */}
</fieldset>
</div>
</div>
);
return registrationComp;
}
}
There can be other lots of ways to do it which I may not be aware.
But I prefer to do change handling in a common method for certain type of common fields such as <input type of text />
This is a sample input field -
<input
onChange = {this.onChange}
value = {this.state.firstName}
type = "text"
name = {"firstName"}
/>
I keep both state's field name and input's "name" attribute same.
After that I write a common change handler for all such fields.
Need to write just one line in change handler.
onChange(e) {
this.setState({[e.target.name]: e.target.value});
}
I am using es6's dynamic property setting from string as property name
{ ["propName] : propValue }.
To avoid writing manual binding for methods in react component you can follow below two approaches -
Use es6 arrow functions
A hack ( I don't know whether this approach is fast or slow but It works :) )
create a method like this in your component.
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
use _bind to bind your methods.
constructor(props){
super(props);
this.state = {
regName : '',
regAdd1 : '',
regAdd2 : '',
regState : '',
regZipCode : '',
regCity : '',
regPhone : ''
};
// add bindings .... ugh..
//this.changeRegAdd1 = this.changeRegAdd1.bind(this);
//this.changeRegAdd2 = this.changeRegAdd2.bind(this);
//Similary binding for other handlers...
this._bind(
'changeRegName',
'changeReg1' , 'changeRegAdd2'
// and so on.
);
}
EDIT :
Adding validation -
Write a method which can iterate over state keys and check if state is empty.
If any of the input field is empty collect the detail about that input and mark that required.
set a state to indicate that form has errors. Specific error details can be found in state's error object.
validateInput() {
let errors = {};
Object.keys(this.state)
.forEach((stateKey) => {
isEmpty(this.state[stateKey]) ? (errors[stateKey] = `*required` ) : null;
});
return {
errors,
isValid : isEmptyObj(errors)
};
}
isFormValid() {
const { errors, isValid } = this.validateInput();
if (!isValid) {
this.setState({ errors});
}
return isValid;
}
onSubmit(e) {
e.preventDefault();
this.setState({errors : {}});
if (this.isFormValid()) {
// Perform form submission
}
}
I have used to utility method calld isEmpty, isEmptyObj. They just check if object is null or undefined or field is empty.
I hope this helps.
You could create a higher order component to handle a lot of that for you. Redux Form has a pretty good pattern you could model it after if you wanted.
You would essentially end up with something like the following (I have not tested this at all, but it should work pretty well):
export class Field extends Component {
handleChange = (event) => this.props.onChange(this.props.name, event.target.value)
render() {
const InputComponent = this.props.component
const value = this.props.value || ''
return (
<InputComponent
{...this.props}
onChange={this.handleChange}
value={value}
/>
}
}
export default function createForm(WrappedComponent) {
class Form extends Component {
constructor() {
super()
this.state = this.props.initialValues || {}
this.handleChange = this.handleChange.bind(this)
}
handleChange(name, value) {
this.setState({
[name]: value,
})
}
render() {
return (
<WrappedComponent
{...this.state}
{...this.props}
// pass anything you want to add here
onChange={this.handleChange}
values={this.state}
/>
)
}
}
return Form
}
Then you can augment this one component as necessary (add focus, blur, submit handlers, etc). You would use it something like this:
import createForm, { Field } from './createForm'
// simplified Registration component
export class Registration extends Component {
render() {
return (
<form>
<Field
component="input"
name="name"
onChange={this.props.onChange}
type="text"
value={this.props.values.name}
/>
</form>
)
}
}
export default createForm(Registration)
You could go crazy and get into context so you don't have to manually pass values and function around, but I would stay away at least until you're more familiar with React.
Also, if you don't want to manually bind functions, you could use the class properties transform if you're using babel. Then the Form component would just look like the following:
class Form extends Component {
state = this.props.initialValues || {}
handleChange = (name, value) => this.setState({
[name]: value,
})
render() {
return (
<WrappedComponent
{...this.state}
{...this.props}
onChange={this.handleChange}
values={this.state}
/>
)
}
}
Take a look at how it's done in NeoForm:
data state is directly mapped to form fields
one onChange handler for the entire form
per-field (for example onBlur) and form (for example onSubmit) validation
plain object and Immutable state helpers
easy integration with Redux or any other state management solution
It uses context internally and really small and modular source code is easy to follow.

How to pass the event argument when binding functions in React?

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.

Wrapping a Component means it loses focus on rerender

I have a long form in react. Before, it had a bunch of components that were defined as such:
<input
type='text'
value={this.state.form.nameOfFormField}
onChange={this.updateForm('nameOfFormField')} />
Where updateForm is a function in the form of (field) => (e) => {}, to make code reuse easier.
I wanted to make this easier to maintain, so I created a component, SpecialInput, which was defined as such:
const SpecialInputBuilder = (form, onChange) => ({ field, ..props }) => (
<input
type='text'
value={form[field]}
onChange={onChange(field)}
{...props} />
)
Now, I could define the Input during render like so:
const SpecialInput = SpecialInputBuilder(this.state.form, this.updateForm)
And use it in the component like this:
<SpecialInput field='nameOfFormField' />
Obviously, this is much more succinct. But this also means that the input field will drop focus every time input is entered into the field (i.e., when updateForm is called), because SpecialInput is defined every time the render function is called. Defining a key to each element does not seem to at all alleviate the problem. How can I fix this while still using this simpler component? Is there a middle ground?
Why not just change your input builder to just be a react component?
const SpecialInput = (props) => {
return (
<input
value={props.form[props.field]}
{...props}
type={props.type || 'text'}
onChange={() => props.onChange(props.field)}
/>
)
}
and just use it the same way.
<SpecialInput field='nameOfFormField' onChange={this.updateForm} form={this.state.form} />
I had a similar approach but ended up changing it to this;
(1) Input typer child component:
import React, { Component } from 'react';
class FreeTextField extends Component {
inputValueFn(e) {
this.props.userInput(this.props.responseObject, e.target.value);
}
render() {
return (
<div className="input-group">
<label>{this.props.buttonText ? this.props.buttonText : "Firstname"}</label>
<input type={this.props.type} placeholder="" className="form-control" defaultValue={this.props.defaultValue} onChange={this.inputValueFn.bind(this)} />
</div>
);
}
}
export default FreeTextField;
(2) From the parent component you can specify all relavent child attr via props
// import
import FreeTextField from './pathTo/FreeTextField';
// Initial state
this.state = {
responseObject: {}
}
// onChange it updates the responseObject
userInput(fieldKey,value) {
let responseObject = this.state.responseObject;
responseObject[fieldKey] = value;
this.setState({responseObject:responseObject});
}
// component render()
<FreeTextField
buttonText="First Name"
type="text"
formObjectKey="first_name"
userInput{this.userInput.bind(this)} />
The main issue is that your onChange call is executing as soon as it's rendered, instead of a reference to a function to be called when the input changes.
// this executes immediately
onChange={onChange(field)}
// this is a reference to the function with a prop prepended
onChange={onChange.bind(this,field)}

Categories

Resources