How to correctly bind function with arguments in component? - javascript

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)

Related

React class component unusual this binding on methods

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.

Input Attribute onChange only if Props is not Undefined

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

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

React.js - input losing focus when rerendering

I am just writing to text input and in onChange event I call setState, so React re-renders my UI. The problem is that the text input always loses focus, so I need to focus it again for each letter :D.
var EditorContainer = React.createClass({
componentDidMount: function () {
$(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
},
componentDidUpdate: function () {
console.log("zde");
$(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
},
changeSelectedComponentName: function (e) {
//this.props.editor.selectedComponent.name = $(e.target).val();
this.props.editor.forceUpdate();
},
render: function () {
var style = {
height: this.props.height + 'px'
};
return (
<div className="container" style={style}>
<div className="row">
<div className="col-xs-6">
{this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
{this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
</div>
<div className="col-xs-6">
<ComponentTree editor={this.props.editor} components={this.props.components}/>
</div>
</div>
</div>
);
}
});
Without seeing the rest of your code, this is a guess.
When you create a EditorContainer, specify a unique key for the component:
<EditorContainer key="editor1"/>
When a re-rendering occurs, if the same key is seen, this will tell React don't clobber and regenerate the view, instead reuse. Then the focused item should retain focus.
I keep coming back here again and again and always find the solution to my elsewhere at the end.
So, I'll document it here because I know I will forget this again!
The reason input was losing focus in my case was due to the fact that I was re-rendering the input on state change.
Buggy Code:
import React from 'react';
import styled from 'styled-components';
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
const Container = styled.div``;
const Input = styled.input``;
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
So, the problem is that I always start coding everything at one place to quickly test and later break it all into separate modules.
But, here this strategy backfires because updating the state on input change triggers render function and the focus is lost.
Fix is simple, do the modularization from the beginning, in other words, "Move the Input component out of render function"
Fixed Code
import React from 'react';
import styled from 'styled-components';
const Container = styled.div``;
const Input = styled.input``;
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
Ref. to the solution: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947
If it's a problem within a react router <Route/> use the render prop instead of component.
<Route path="/user" render={() => <UserPage/>} />
The loss of focus happens because the component prop uses React.createElement each time instead of just re-rendering the changes.
Details here: https://reacttraining.com/react-router/web/api/Route/component
I had the same symptoms with hooks. Yet my problem was defining a component inside the parent.
Wrong:
const Parent =() => {
const Child = () => <p>Child!</p>
return <Child />
}
Right:
const Child = () => <p>Child!</p>
const Parent = () => <Child />
My answer is similar to what #z5h said.
In my case, I used Math.random() to generate a unique key for the component.
I thought the key is only used for triggering a rerender for that particular component rather than re-rendering all the components in that array (I return an array of components in my code). I didn't know it is used for restoring the state after rerendering.
Removing that did the job for me.
Applying the autoFocus attribute to the input element can perform as a workaround in situations where there's only one input that needs to be focused. In that case a key attribute would be unnecessary because it's just one element and furthermore you wouldn't have to worry about breaking the input element into its own component to avoid losing focus on re-render of main component.
What I did was just change the value prop to defaultValue and second change was onChange event to onBlur.
I got the same behavior.
The problem in my code was that i created a nested Array of jsx elements like this:
const example = [
[
<input value={'Test 1'}/>,
<div>Test 2</div>,
<div>Test 3</div>,
]
]
...
render = () => {
return <div>{ example }</div>
}
Every element in this nested Array re-renders each time I updated the parent element. And so the inputs lose there "ref" prop every time
I fixed the Problem with transform the inner array to a react component
(a function with a render function)
const example = [
<myComponentArray />
]
...
render = () => {
return <div>{ example }</div>
}
EDIT:
The same issue appears when i build a nested React.Fragment
const SomeComponent = (props) => (
<React.Fragment>
<label ... />
<input ... />
</React.Fragment>
);
const ParentComponent = (props) => (
<React.Fragment>
<SomeComponent ... />
<div />
</React.Fragment>
);
I solved the same issue deleting the key attribute in the input and his parent elements
// Before
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
key={ Math.random }
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
// After
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
The answers supplied didn't help me, here was what I did but I had a unique situation.
To clean up the code I tend to use this format until I'm ready to pull the component into another file.
render(){
const MyInput = () => {
return <input onChange={(e)=>this.setState({text: e.target.value}) />
}
return(
<div>
<MyInput />
</div>
)
But this caused it to lose focus, when I put the code directly in the div it worked.
return(
<div>
<input onChange={(e)=>this.setState({text: e.target.value}) />
</div>
)
I don't know why this is, this is the only issue I've had with writing it this way and I do it in most files I have, but if anyone does a similar thing this is why it loses focus.
If the input field is inside another element (i.e., a container element like <div key={"bart"}...><input key={"lisa"}...> ... </input></div>-- the ellipses here indicating omitted code), there must be a unique and constant key on the container element (as well as on the input field). Elsewise, React renders up a brand new container element when child's state is updated rather than merely re-rendering the old container. Logically, only the child element should be updated, but...
I had this problem while trying to write a component that took a bunch of address information. The working code looks like this
// import react, components
import React, { Component } from 'react'
// import various functions
import uuid from "uuid";
// import styles
import "../styles/signUp.css";
export default class Address extends Component {
constructor(props) {
super(props);
this.state = {
address1: "",
address2: "",
address1Key: uuid.v4(),
address2Key: uuid.v4(),
address1HolderKey: uuid.v4(),
address2HolderKey: uuid.v4(),
// omitting state information for additional address fields for brevity
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
event.preventDefault();
this.setState({ [`${event.target.id}`]: event.target.value })
}
render() {
return (
<fieldset>
<div className="labelAndField" key={this.state.address1HolderKey} >
<label className="labelStyle" for="address1">{"Address"}</label>
<input className="inputStyle"
id="address1"
name="address1"
type="text"
label="address1"
placeholder=""
value={this.state.address1}
onChange={this.handleChange}
key={this.state.address1Key} ></input >
</div>
<div className="labelAndField" key={this.state.address2HolderKey} >
<label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
<input className="inputStyle"
id="address2"
name="address2"
type="text"
label="address2"
placeholder=""
key={this.state.address2Key} ></input >
</div>
{/* omitting rest of address fields for brevity */}
</fieldset>
)
}
}
Sharp-eyed readers will note that <fieldset> is a containing element, yet it doesn't require a key. The same holds for <> and <React.Fragment> or even <div> Why? Maybe only the immediate container needs a key. I dunno. As math textbooks say, the explanation is left to the reader as an exercise.
I had this issue and the problem turned out to be that I was using a functional component and linking up with a parent component's state. If I switched to using a class component, the problem went away. Hopefully there is a way around this when using functional components as it's a lot more convenient for simple item renderers et al.
I just ran into this issue and came here for help. Check your CSS! The input field cannot have user-select: none; or it won't work on an iPad.
The core reason is: When React re-render, your previous DOM ref will be invalid. It mean react has change the DOM tree, and you this.refs.input.focus won't work, because the input here doesn't exist anymore.
For me, this was being caused by the search input box being rendered in the same component (called UserList) as the list of search results. So whenever the search results changed, the whole UserList component rerendered, including the input box.
My solution was to create a whole new component called UserListSearch which is separate from UserList. I did not need to set keys on the input fields in UserListSearch for this to work. The render function of my UsersContainer now looks like this:
class UserContainer extends React.Component {
render() {
return (
<div>
<Route
exact
path={this.props.match.url}
render={() => (
<div>
<UserListSearch
handleSearchChange={this.handleSearchChange}
searchTerm={this.state.searchTerm}
/>
<UserList
isLoading={this.state.isLoading}
users={this.props.users}
user={this.state.user}
handleNewUserClick={this.handleNewUserClick}
/>
</div>
)}
/>
</div>
)
}
}
Hopefully this helps someone too.
I switched value prop to defaultValue. That works for me.
...
// before
<input value={myVar} />
// after
<input defaultValue={myVar} />
My problem was that I named my key dynamically with a value of the item, in my case "name" so the key was key={${item.name}-${index}}. So when I wanted to change the input with item.name as the value, they key would also change and therefore react would not recognize that element
included the next code in tag input:
ref={(input) => {
if (input) {
input.focus();
}
}}
Before:
<input
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
After:
<input
ref={(input) => {
if (input) {
input.focus();
}
}}
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
I had a similar issue, this is fixed it.
const component = () => {
return <input onChange={({target})=>{
setValue(target.vlaue)
}
} />
}
const ThisComponentKeptRefreshingContainer = () => {
return(
<component />
)
}
const ThisContainerDidNot= () => {
return(
<> {component()} </>
)
}
As the code illustrate calling the component child like an element gave that re-rendering effect, however, calling it like a function did not.
hope it helps someone
I had the same problem with an html table in which I have input text lines in a column. inside a loop I read a json object and I create rows in particular I have a column with inputtext.
http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/
I managed to solve it in the following way
import { InputTextComponent } from './InputTextComponent';
//import my inputTextComponent
...
var trElementList = (function (list, tableComponent) {
var trList = [],
trElement = undefined,
trElementCreator = trElementCreator,
employeeElement = undefined;
// iterating through employee list and
// creating row for each employee
for (var x = 0; x < list.length; x++) {
employeeElement = list[x];
var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
trList.push(trNomeImpatto);
trList.push(trElementCreator(employeeElement, 0, x));
trList.push(trElementCreator(employeeElement, 1, x));
trList.push(trElementCreator(employeeElement, 2, x));
} // end of for
return trList; // returns row list
function trElementCreator(obj, field, index) {
var tdList = [],
tdElement = undefined;
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}
//updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
/>
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);
var trComponent = createClass({
render: function () {
return React.createElement('tr', null, tdList);
}
});
return React.createElement(trComponent);
} // end of trElementCreator
});
...
//my tableComponent
var tableComponent = createClass({
// initial component states will be here
// initialize values
getInitialState: function () {
return {
impattiComposite: [],
serviceId: window.sessionStorage.getItem('serviceId'),
serviceName: window.sessionStorage.getItem('serviceName'),
form_data: [],
successCreation: null,
};
},
//read a json data soure of the web api url
componentDidMount: function () {
this.serverRequest =
$.ajax({
url: Url,
type: 'GET',
contentType: 'application/json',
data: JSON.stringify({ id: this.state.serviceId }),
cache: false,
success: function (response) {
this.setState({ impattiComposite: response.data });
}.bind(this),
error: function (xhr, resp, text) {
// show error to console
console.error('Error', xhr, resp, text)
alert(xhr, resp, text);
}
});
},
render: function () {
...
React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
...
}
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}//impattiComposite = my json data source
/>//end my input text
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);//add a component
//./InputTextComponent.js
import React from 'react';
export class InputTextComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
idImpatto: props.idImpatto,
value: props.value,
noteType: props.noteType,
_impattiComposite: props.impattiComposite,
};
this.updateNote = this.updateNote.bind(this);
}
//Update a inpute text with new value insert of the user
updateNote(event) {
this.setState({ value: event.target.value });//update a state of the local componet inputText
var impattiComposite = this.state._impattiComposite;
var index = this.state.idImpatto - 1;
var impatto = impattiComposite[index];
impatto[this.state.noteType] = event.target.value;
this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)
}
render() {
return (
<input
className="Form-input"
type='text'
value={this.state.value}
onChange={this.updateNote}>
</input>
);
}
}
Simple solution in my case:
<input ref={ref => ref && ref.focus()}
onFocus={(e)=>e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length)}
/>
ref triggers focus, and that triggers onFocus to calculate the end and set the cursor accordingly.
The issue in my case was that the key prop values I was setting on the InputContainer component and the input fields themselves were generated using Math.random(). The non-constant nature of the values made it hard for track to be kept of the input field being edited.
For me I had a text area inside a portal. This text area was loosing focus. My buggy portal implementation was like this:
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const divRef = useRef(document.createElement('div'));
useEffect(()=>{
const ref = divRef.current;
modalDOM?.appendChild(ref);
return ()=>{
modalDOM?.removeChild(ref);
}
});
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
}
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
divRef.current)
)
}
const Parent = ({content: string}: ParentProps) => {
const [content, setContent] = useState<string>(content);
const onChangeFile = (e: React.MouseEvent) => {
setContent(e.currentTarget.value);
}
return (
<Modal>
<textarea
value={content}
onChange={onChangeFile}>
</textarea>
</Modal>
)
}
Turned out following implementation worked correctly, here I am directly attaching modal component to the DOM element.
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
modalDOM || document.body)
)
}
Turns out I was binding this to the component which was causing it to rerender.
I figured I'd post it here in case anyone else had this issue.
I had to change
<Field
label="Post Content"
name="text"
component={this.renderField.bind(this)}
/>
To
<Field
label="Post Content"
name="text"
component={this.renderField}
/>
Simple fix since in my case, I didn't actually need this in renderField, but hopefully me posting this will help someone else.
Changing text in the input of some control can cause parent control rerendering in some cases (according to binding to props).
In this case focus will be lost. Editing should not has effect to parent container in DOM.

Categories

Resources