Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v6] fields losing focus after first onChange #1094

Closed
andrewmclagan opened this issue Jun 2, 2016 · 40 comments
Closed

[v6] fields losing focus after first onChange #1094

andrewmclagan opened this issue Jun 2, 2016 · 40 comments

Comments

@andrewmclagan
Copy link

andrewmclagan commented Jun 2, 2016

Using version 6.0.0-alpha.13 we are encountering behaviour that is hard to debug.

Rendering <Field /> components that have the component={props => ... syntax we experience the following: On entering text into the input field, it loses focus after the first key press.

This behaviour does not occur when rendering the component as: <Field component="input" type="text" /> OR after the first character is inserted and any field is refocused

The onChange event and all other event inherited through context seem to be there when inspect. Test with both versions 6.0.0-alpha.13 and 6.0.0-alpha.14. Dependancies that may / may not be effecting the issue: react-bootstrap, react 15.1.0.

There seem to be issues with focus changing on various other circumstances, tabs also not jumping to the correct component.

redux-form-issue

@andrewmclagan andrewmclagan changed the title [v6] fields loosing focus after first onChange [v6] fields losing focus after first onChange Jun 2, 2016
@andrewmclagan
Copy link
Author

andrewmclagan commented Jun 2, 2016

** FACE PALM ***

My bad, just traced it through the stack and discovered that it was simply re-rendering the component...

DUH ITS STATLESS

Basically for those who want to know we are cutting our boiler plate by creating a component to wrap error validation, label and other stuff. Also saves on our build size of course :-)

export default class FormField extends Component {

  field = props => {
    return (
      <div>
        {props.label && <label htmlFor={props.id}>{props.label}</label>}
        {createElement(props.componentToRender, props)}
        {props.touched && props.error && <div className="text-danger">{props.error}</div>}
      </div>
    );
  }

  render() {

    const componentToRender = this.props.component;

    return <Field {...this.props} {...componentToRender} component={this.field} />;
  }
}

@beornborn
Copy link

It is not the explanation.

http://redux-form.com/6.0.0-alpha.13/examples/material-ui/
here we see it working without any wrappers.

@beornborn
Copy link

I have this issue only in [email protected]
in [email protected] everything ok

@OKNoah
Copy link

OKNoah commented Jun 15, 2016

I have this same issue in alpha.13.

export default class Register extends Component {
  generateInput (field, type) {
    return (
      <div>
        { field.error &&
          <span className={ classes.validationError }>
            { field.error }
          </span>
        }
        <input
          type={ type || 'text' }
          {...field}
        />
      </div>
    );
  }

  render () {
    return (
      <form
        className={ classes.registerForm }
        onSubmit={handleSubmit}
      >
        <div className="form-group">
          <label>Username</label>
          <Field
            name="username"
            type="text"
            className="form-control"
            component={ username => this.generateInput(username) }
          />
        </div>
      </form>
    );
  }
}

@erikras
Copy link
Member

erikras commented Jun 15, 2016

component={ username => this.generateInput(username) }

You can't do this. This is constructing a new component function on every render.

It must be

component={ this.generateInput }

@spaceoi
Copy link

spaceoi commented Jun 16, 2016

Exactly I also met this issue in [email protected]
like @beornborn said, in [email protected] everything ok

This issue should be be reopened, @andrewmclagan

@ooflorent
Copy link
Contributor

ooflorent commented Jun 16, 2016

@spaceoi It should not. Please read the release note of v6.0.0-alpha.14.

@beornborn
Copy link

@ooflorent I've read several times but haven't seen any notice about losing focus bug.

@beornborn
Copy link

By the way, in my another application everything works fine, I implemented both them at the same time, but in one case this bug present, in another not. And I have no idea why so and how to debug this.

@ooflorent
Copy link
Contributor

The losing focus bug is not a bug. It's the expected React behavior. If you are defining your component as an arrow function on every render, React is not able to reconcile the DOM and assume it is a new component because the type property of the previous and the new React element is different. If they are, React will remove the linked DOM tree and create a new one. Which implies that the rendered DOM input element is not the previous one and cannot restore the focus or the selection.

@ooflorent
Copy link
Contributor

@beornborn Related discussions: #961, #961 (comment) and #961 (comment)

@beornborn
Copy link

beornborn commented Jun 16, 2016

@ooflorent But why then another changes of this field don't remove focus, and other changes of other fields don't remove focus. And I tried implementation without arrow functions - the same problem. And I didn't change anything, just downgraded the version to 13, and everything works fine!

@ooflorent
Copy link
Contributor

ooflorent commented Jun 16, 2016

And I tried implementation without arrow functions - the same problem.

This is an issue. Could you create a minimal sample to reproduce this and paste it here (or link a gist) ?

@beornborn
Copy link

@ooflorent ok, I'll do it in few hours

@beornborn
Copy link

beornborn commented Jun 16, 2016

@ooflorent
I couldn't reproduce not working without arrow functions example.

But I reproduced situation when it works on 6.0.13 with arrow links.
https://www.youtube.com/watch?v=mWS1ZvmyIZc
I am just changing version and it begins to work.

@luisrudge
Copy link
Contributor

Wow. I'm having this issue too! When I type the first char, it loses focus, after that it doesn't.

@spaceoi
Copy link

spaceoi commented Jun 17, 2016

Thanks @ooflorent
I recommend, for any one who met this problem, could read the release note of v6.0.0-alpha.14 with demo in alpha.13 and demo in alpha.15.

jordanh added a commit to ParabolInc/parabol that referenced this issue Jun 29, 2016
@OKNoah
Copy link

OKNoah commented Jun 30, 2016

Some more clarification for anyone struggling:

  • You cannot use the component={field => arrow function method that's in the examples.
  • You cannot put the input generating function in the render() function
  • You cannot put the function anywhere in your component class. So no component={this.generateMyInput}
  • You can put it in your MyComponent.jsx file, just before you declare your export class MyComponent extends React.Component { class.
  • You can pass it in as a prop from another component. As in <SomeComponent inputRenderer={::this.renderInput} /> then in SomeComponent <Field component={this.props.inputRenderer}

Those are my observations. I don't know if the last one is expected behaviour, so use at your own risk as maybe it will break in the future?

EDIT: Still unsure how to get this.props into that function since it's outside the component. If you do component={renderInput.bind(null, this.props), you get the losing focus problem again.

@fenos
Copy link

fenos commented Jul 27, 2016

any suggestion on this on version 6.0.0-rc.3 having the same issue

EDIT:
@OKNoah I found the solution :)

To pass custom props to your renderInput function outside of the class, add them to the <Field> component. like: <Field customProp="hello" {...fieldProps}>

In your function

function renderInput(props) {
   console.log(props.input.customProp); // here you access the props
}

@GuillaumeCisco
Copy link

I encountered the same problem with material-ui and using a form in a dialog.
Trick was to create a Input component and calling it this way:

import React, {Component} from 'react';
import TextField from 'material-ui/TextField';

const TextInput = props =>
    <TextField errorText={props.touched && props.error}
               {...props.input}
    />;

export default TextInput
<Field name="my_field" component={TextInput} type="text" placeholder="My placeholder"  value={this.props.my_value || ''}/>

@hanahub
Copy link

hanahub commented Sep 30, 2016

If anyone still gets this error, please check this issue.
Don't use react-css-modules on redux-form components.

@godmar
Copy link

godmar commented Oct 19, 2016

The example in the migration guide:
http://redux-form.com/6.1.1/docs/MigrationGuide.md/

Specifically this line:

<input {...field.input} type={field.type}/>  // Type specified below in <Field>

is not valid JSX because of the <Field> inside the comment.

@xander9112
Copy link

xander9112 commented Feb 10, 2017

I'm using last version 6.5.0
and i have this problem, a also find this;
If I write this:
<Field {...this.props} component={::this.renderField}/>
I have problem with focus,
BUT if

<Field {...this.props} component={this.renderField}/>
I don't have problem with focus

@gustavohenke
Copy link
Collaborator

Hi @xander9112, ::this.renderField is effectively creating a new function everytime it's run.
It's the same as this.renderField.bind(this), if I'm not mistaken.

@reznord
Copy link

reznord commented Feb 13, 2017

@gustavohenke I am getting the same issue and here is a small example of my implementation. I am getting this only when I wrap the component in a HOC (which in my case is Popup.jsx). Can you tell me where I am going wrong? I am loosing the focus on the first onChange in the TextField.

@cssModules({
  ...styles,
  ...config,
})
class TextField extends Component {
  render() {
    const {
      label,
      cursor,
      input: {
        onChange,
        onFocus,
        onBlur: reduxOnBlur,
        value,
      },
      meta: {
        error,
        touched,
      },
      onBlur,
      readOnly,
      description,
      className,
      orientation,
      fieldArray,
      name,
      popup,
      password,
      upload,
      placeholder,
      page,
    } = this.props;

    const composedOnBlur = compose(reduxOnBlur, onBlur
      ? onBlur
      : f => f);

    const component = (
      <span>
        <input
          type="text"
          value={value}
          onFocus={onFocus}
          readOnly={readOnly}
          placeholder={placeholder}
          autoFocus={cursor ? true : false}
          styleName={page ? 'input' : 'input-popup'}
          onBlur={() => composedOnBlur(value, name)}
          onChange={event => onChange(event.target.value)}
        />
        {fieldArray === true ?
          null
          :
          <div className="error-field">
            {touched && error && <span styleName="error">{error}</span>}
          </div>
        }
      </span>
    );
    const textFieldHorizontal = makeHorizontalComponent(
      className,
      label,
      description,
      component,
      popup,
      upload,
    );

    return textFieldHorizontal;
  }
}

export default TextField;

and this is how I am using the TextField component:

<Popup
  title="Voicemail"
  resetForm={resetForm}
  formName={this.formName}
  initialState={initialForm}
  currentFormValues={formValues}
  initializeForm={initializeForm}
  onButtonClick={this.handleVoicemailValues}
>
  <Field
    component={TextField}
    name="voicemail.email"
    label="Email Address"
    className="row-popup" popup
    syncErrors={errors && errors.voicemail ? errors.voicemail.email : ''}
    description="The voice message will be emailed in an MP3 format to this address"
  />
</Popup>

just for reference, this is my popup.jsx

@cssModules(styles)
class Popup extends Component {
  constructor(props) {
    super(props);
    this.handleDeleteClick = this.handleDeleteClick.bind(this);
  }

  componentDidMount() {
    const { initializeForm, currentFormValues, formName } = this.props;
    initializeForm(formName, currentFormValues);
  }

  handleCancelClick() {
    const { resetForm, formName, closePortal, onPopupCloseClick } = this.props;
    typeof onPopupCloseClick === 'function' ? onPopupCloseClick() : null;
    resetForm(formName);
    closePortal();
  }

  handleButtonClick() {
    const {
      closePortal,
      onButtonClick,
    } = this.props;
    typeof onButtonClick === 'function' ? onButtonClick() : null;
    closePortal();
  }

  render() {
    const {
      title,
      subtitle,
      fullwidth,
      alertType,
      alertName,
      fullscreen,
      deleteText,
      deletePopup,
      buttonLabel,
      popupDelete,
      popupSubmit,
      searchField,
    } = this.props;

    const fullpage = (
      <div styleName="overlay">
        <div styleName="full-container">
          {this.props.children}
        </div>
      </div>
    );

    const popup = (
      <div styleName="overlay">
        <div styleName="container">
          {title ?
            <div styleName="title-container">
              <span styleName="title">{title}</span>
              {subtitle &&
              <span>
                <span styleName="dash">–</span>
                <span styleName="subtitle">{subtitle}</span>
              </span>
              }
              <span styleName="close" onClick={:: this.handleCancelClick} className="icon-close" />
            </div>
          :
          null
        }
          <div styleName='content'>
            {this.props.children}
          </div>

          <div styleName="footer-container">
            <Button onButtonClick={:: this.handleButtonClick} label={buttonLabel} />
          </div>
        </div>
      </div>
    );

    return (
      <div>
        {popup}
      </div>
    );
  }
}

export default Popup;

Here is what i am getting in the redux dev tools:

screen shot 2017-02-13 at 3 03 49 pm

@i6mi6
Copy link

i6mi6 commented Jun 4, 2017

For me it was .bind(this) that caused the problem: component={renderInput.bind(this)}

@mir3z
Copy link

mir3z commented Aug 14, 2017

What's the status of this ticket and why it's closed? I still have this issue in v.7.

@iTonyYo
Copy link

iTonyYo commented Aug 17, 2017

@ooflorent Do you know how to solve this problem? Fields losing focus after first onChange, so I stopped using class & render(), then got this problem... I tried to use prop-types, but it dosen't work.

image

"react": "^15.6.1",
"redux": "^3.7.2",
"redux-form": "^7.0.3",

./src/view/ViewSignin/FormSignin.js

'use strict';

import React, { Component } from 'react';
import Radium from 'radium';
import { Link } from 'react-router-dom';
import {
  Field,
  reduxForm
} from 'redux-form';

const renderField = ({
  input,
  type,
  label
}) => (
  <div>
    <input { ...input } type={ type } placeholder={ label } />
  </div>
);

const FormSignin = (props) => {
  const { handleSubmit } = props;
  return (
    <form className='frm-signin' onSubmit={ handleSubmit }>

      <div className='border-box xy-center row field-area'>
        <div className='fields'>
          <Field
          name="userAccount"
          component={ renderField }
          type="text"
          label='电子邮件 / 手机号'/>

          <i></i>

          <Field
          name="userPassword"
          component={ renderField }
          type="password"
          label='密码'/>
        </div>
      </div>

      <div className='border-box xy-center row button-area'>
        <div>
          <button
            type="submit">
            <span className='xy-center'>
              登录
            </span>
          </button>
        </div>
      </div>

      <div className='border-box row others-area'>
        <Field
          className='chk-keep-signin'
          name="keepSignin"
          id="keep-signin"
          component="input"
          type="checkbox"
        />

        <label className='lbl-keep-signin' htmlFor="keep-signin">
          保持登录
        </label>

        <Link className='right btn-signup'
              to='signin'>
          立即注册
        </Link>

        <Link className='right clean-link btn-forget-password'
              to='signin'>
          忘记密码
        </Link>
      </div>

    </form>
  );
}

export default reduxForm({
  form: 'signin'
})(FormSignin);

@majiddadashi
Copy link

If you still have this problem, you're probably trying to send some properties to your component and you are mistakenly doing it by passing them as arguments to the function in front of the component.
like this:

<FieldArray name="members" props={{verb}} component={renderInput.bind(null, someProp} />
//or
<FieldArray name="members" props={{verb}} component={renderInput(someProb)} /> 

which regenerates the component every time (which seems like the component is just losing focus).

The correct way is to use the props property of Field or FieldArray or whatever redux-form component you are using. Like this:

<FieldArray name="members" props={{someProp}} component={renderMemebrs} /> 

Then inside renderMembers, get the property like this:

const renderMemebrs = (props) => {
  let {someProp} = props;
...

@cavaloni
Copy link

I am not passing props, not using a function in render and I still have this problem.

@nucab
Copy link

nucab commented Sep 18, 2017

try to wrap the class component with the CSSModules inside reduxForm instead of using decorator

@cavaloni
Copy link

This particular component and its parents are not using css modules.

@real-ashwin
Copy link

Thanks @i6mi6. It was .bind for me too.
I had to change from component={this.getComponent.bind(this)} to binding in constructor.

constructor(props) {
    super(props);
    this.getComponent = this.getComponent.bind(this);
  }

`component={this.getComponent}`

@softwarefactory1
Copy link

softwarefactory1 commented Mar 29, 2018

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

@ehildt
Copy link

ehildt commented Aug 28, 2018

I just came across the same issue. Reading this blog helped me fixing it, thank you OkNoah!
As mentioned above it is not a bug but a desired effect from react. So if you want your own component then make sure you declare it in another file and import it or declare it in the same file but out of your stateful/stateless component. React recognizes new components/arrays/objects etc. based on their reference/key given thus if it's a new ref. then react will drop the old and use the new whatsoever object!

@phsantiago
Copy link

phsantiago commented Jun 21, 2019

I had the same problem trying to pass down a prop to my custom react native input like:

        <Field
          name="email"
          component={props => <EmailField dark {...props} />}
        />

Solved this way:

        <Field
          name="email"
          dark
          component={EmailField}
        />

I changed the place of dark prop. Redux-form pass all extra props of Field to the component={EmailField}

@nikbelikov
Copy link
Contributor

** FACE PALM ***

My bad, just traced it through the stack and discovered that it was simply re-rendering the component...

DUH ITS STATLESS

Basically for those who want to know we are cutting our boiler plate by creating a component to wrap error validation, label and other stuff. Also saves on our build size of course :-)

export default class FormField extends Component {

  field = props => {
    return (
      <div>
        {props.label && <label htmlFor={props.id}>{props.label}</label>}
        {createElement(props.componentToRender, props)}
        {props.touched && props.error && <div className="text-danger">{props.error}</div>}
      </div>
    );
  }

  render() {

    const componentToRender = this.props.component;

    return <Field {...this.props} {...componentToRender} component={this.field} />;
  }
}

It's worked to me too) Rerender - this is the clue.

@Enzo-Migliardi
Copy link

component={ username => this.generateInput(username) }

You can't do this. This is constructing a new component function on every render.

It must be

component={ this.generateInput }

You have helped someone 4 years after this comment, thank you :)

@thiagomatrix
Copy link

thiagomatrix commented Aug 6, 2020

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={"[email protected]"}
      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={"[email protected]"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>

@kavilivishnu
Copy link

kavilivishnu commented Sep 18, 2022

To everyone who are facing this issue currently or might be in future, of onChange not firing inside a component that we are firing by rendering that component into other component, I found a temporary solution that I'm writing down here. But I still wanna know why we can't use setState inside this onChange function. If there's any solution with anyone, please do let me know. Thanks! FYI - @andrewmclagan

import "./styles.css";
import React, { useState, useEffect } from "react";
import { TextArea } from "carbon-components-react";

export default function App() {
  let updatedArray = [];
  const [testArray, setTestArray] = useState(updatedArray);

  const Test = (props) => {
    const { id, sampleText } = props;
    return (
      <div>
        <TextArea
          name="sampleText"
          id="sampleText"
          onChange={(e) => {
            const { name, value } = (e && e.target) || {};
            const editArray = testArray.map((item) => {
              return item;
            });
            editArray[id][name] = value;
            updatedArray = editArray;
          }}
          defaultValue={sampleText}
        />
      </div>
    );
  };

  const viewFinal = () => {
    console.log(testArray);
    // you can send the updatedArray to backend here if needed
  };

  const add = () => setTestArray([...testArray, {}]);

  // useEffect(() => {
  //   receive adat from backend and set to current array state here
  //   setTestArray(dataFromBackend)
  // },[])

  return (
    <div className="App">
      <button onClick={(e) => add(e)}>add</button>
      {testArray && Array.isArray(testArray) && testArray.length > 0 && (
        <div>
          {testArray.map((items, id) => {
            const { sampleText = "" } = items || {};
            return <Test id={id} sampleText={sampleText} />;
          })}
        </div>
      )}
      <button onClick={viewFinal}>view Curr Array</button>
    </div>
  );
}

To view it in action, go over to - https://codesandbox.io/s/optimistic-mopsa-m12wmv

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests