91
votes

In my component below, the input field loses focus after typing a character. While using Chrome's Inspector, it looks like the whole form is being re-rendered instead of just the value attribute of the input field when typing.

I get no errors from either eslint nor Chrome Inspector.

Submitting the form itself works as does the actual input field when it is located either in the render's return or while being imported as a separate component but not in how I have it coded below.

Why is this so?

Main Page Component

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionPost from '../redux/action/actionPost';
import InputText from './form/InputText';
import InputSubmit from './form/InputSubmit';

class _PostSingle extends Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            post: {
                title: '',
            },
        };
        this.onChange = this.onChange.bind(this);
        this.onSubmit = this.onSubmit.bind(this);
    }
    onChange(event) {
        this.setState({
            post: {
                title: event.target.value,
            },
        });
    }
    onSubmit(event) {
        event.preventDefault();
        this.props.actions.postCreate(this.state.post);
        this.setState({
            post: {
                title: '',
            },
        });
    }
    render() {
        const onChange = this.onChange;
        const onSubmit = this.onSubmit;
        const valueTitle = this.state.post.title;
        const FormPostSingle = () => (
            <form onSubmit={onSubmit}>
                <InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
                <InputSubmit name="Save" />
            </form>
        );
        return (
            <main id="main" role="main">
                <div className="container-fluid">
                    <FormPostSingle />
                </div>
            </main>
        );
    }
}

_PostSingle.propTypes = {
    actions: PropTypes.objectOf(PropTypes.func).isRequired,
};

function mapStateToProps(state) {
    return {
        posts: state.posts,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(actionPost, dispatch),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(_PostSingle);

Text Input Component

import React, { PropTypes } from 'react';

const InputText = ({ name, label, placeholder, onChange, value, error }) => {
    const fieldClass = 'form-control input-lg';
    let wrapperClass = 'form-group';
    if (error && error.length > 0) {
        wrapperClass += ' has-error';
    }
    return (
        <div className={wrapperClass}>
            <label htmlFor={name} className="sr-only">{label}</label>
            <input type="text" id={name} name={name} placeholder={placeholder} onChange={onChange} value={value} className={fieldClass} />
            {error &&
                <div className="alert alert-danger">{error}</div>
            }
        </div>
    );
};

InputText.propTypes = {
    name: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    placeholder: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired,
    value: PropTypes.string,
    error: PropTypes.string,
};

InputText.defaultProps = {
    value: null,
    error: null,
};

export default InputText;

Submit Button Component

import React, { PropTypes } from 'react';

const InputSubmit = ({ name }) => {
    const fieldClass = 'btn btn-primary btn-lg';
    return (
        <input type="submit" value={name} className={fieldClass} />
    );
};

InputSubmit.propTypes = {
    name: PropTypes.string,
};

InputSubmit.defaultProps = {
    name: 'Submit',
};

export default InputSubmit;
20

20 Answers

69
votes

it is because you are rendering the form in a function inside render().

Every time your state/prop change, the function returns a new form. it caused you to lose focus.

Try putting what's inside the function into your render directly.

       <main id="main" role="main">
            <div className="container-fluid">
                <FormPostSingle />
            </div>
        </main>

====>

       <main id="main" role="main">
            <div className="container-fluid">
                <form onSubmit={onSubmit}>
                    <InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
                    <InputSubmit name="Save" />
                </form>
            </div>
        </main>
33
votes

What's happening is this:

When your onChange event fires, the callback calls setState with the new title value, which gets passed to your text field as a prop. At that point, React renders a new component, which is why you lose focus.

My first suggestion would be to provide your components keys, particularly the form and the input itself. Keys allow React to retain the identity of components through renders.

Edit:

See this documentation on keys: https://reactjs.org/docs/lists-and-keys.html#keys

31
votes

This happened to me although I had keys set!

Here's why:

I was using a key from a text field. Inside the same block; I had an input field to update the value of the same text field. Now, since component keys are changing, react re-renders the UI. Hence loosing focus.

What to take from this:

Don't use keys which are constantly changing.

14
votes

Had the same issue and solved it in a quick & easy manner: just calling the component with {compName()} instead of <compName />

For instance, if we had:

const foo = ({param1}) => {
   // do your stuff
   return (
      <input type='text' onChange={onChange} value={value} />
   );
};

const main = () => (
   <foo param1={true} />
);

Then, we just need to change the way we call the foo() component:

const main = () => (
   {foo({param1: true})}
);
10
votes

You have to use a unique key for the input component.

<input key="random1" type="text" name="displayName" />

The key="random1" cannot be randomly generated. For example,

<div key={uuid()} className='scp-ren-row'>

uuid() will generate a new set of string for each rerender. This will cause the input to lose focus.

If the elements are generated within a .map() function, use the index to be part of the key.

{rens.map((ren,i)=>{
    return(
    <div key={`ren${i+1}`} className='scp-ren-row'>
       {ren}{i}
</div>)
}

This will solve the issue.

3
votes

My issue was it was rerendering in a stateless component in the same file. So once I got rid of that unecessary stateless component and just put the code in directly, I didn't have unecessary rerenders

render(){
   const NewSocialPost = () => 
       <div className='new-post'>
           <input
                onChange={(e) => this.setState({ newSocialPost: e.target.value })}
                value={this.state.newSocialPost}/>
           <button onClick={() => this._handleNewSocialPost()}>Submit</button>
      </div>

return (
            <div id='social-post-page'>
                <div className='post-column'>
                    <div className='posts'>
                        <Stuff />
                    </div>
                    <NewSocialPost />
                </div>
                <MoreStuff />
            </div>
3
votes

I also had this problem, my problem was related to using another component to wrap the textarea.

// example with this problem
import React from 'react'

const InputMulti = (props) => {
  const Label = ({ label, children }) => (
    <div>
      <label>{label}</label>
      { children }
    </div>
  )

  return (
    <Label label={props.label}>
      <textarea
        value={props.value}
        onChange={e => props.onChange(e.target.value)}
      />
    </Label>
  )
}

export default InputMulti

when the state changed, react would render the InputMulti component which would redefine the Label component every time, meaning the output would be structurally the same, but because of JS, the function would be considered !=.

My solution was to move the Label component outside of the InputMulti component so that it would be static.

// fixed example
import React from 'react'

const Label = ({ label, children }) => (
  <div>
    <label>{label}</label>
    { children }
  </div>
)

const InputMulti = (props) => {
  return (
    <Label label={props.label}>
      <textarea
        value={props.value}
        onChange={e => props.onChange(e.target.value)}
      />
    </Label>
  )
}

export default InputMulti

I've noticed that people often place locally used components inside the component that wants to use it. Usually to take advantage of function scope and gain access to the parent component props.

const ParentComp = ({ children, scopedValue }) => {
  const ScopedComp = () => (<div>{ scopedValue }</div>)
  return <ScopedComp />
}

I never really thought of why that would be needed, since you could just prop-drill the props to the internal function and externalise it from the parent comp.

This problem is a perfect example of why you should always externalise your components from each other, even if they are used in one module. Plus you can always use smart folder structures to keep things close by.

src/
  components/
    ParentComp/
      ParentComp.js
      components/
        ScopedComp.js
2
votes

I'm new to React, and have been running into this issue.

Here's what I did to solve:

  1. First move all of your components into your components folder and then import them where you want to use them
  2. Make sure all of your form elements get a name and id property
  3. Make sure all components as you walk up the tree get a unique key

Someone smarter than me can probably tell us why we can skip step one and keep everything inline so to speak, but this just helped me organize the code.

I think the real issue is React is rerendering everything (as already stated) and sometimes that rerender is happening on a parent component that doesn't have a key but needs one.

My problem was with ExpansionPanel components wrapping my custom components for form inputs. The panels needed key as well!

Hope this helps someone else out there, this was driving me crazy!

1
votes

By adding

autoFocus="autoFocus"

in the input worked for me

<input
  type="text"
  autoFocus="autoFocus"
  value = {searchString}
  onChange = {handleChange}
/>
1
votes

The problem is with dynamic render() caused by useState() function so you can do this for example. in this code you should use onChange() to get just the new updated data and onMouseLeave() to handle the update but with condition that data is changed to get better performance

example child

         export default function Child(){
        const [dataC,setDataC]=useState()
        return(<Grid>
        <TextField 
        .
        .
        onChange={(r)=> setDataC(r.target.value) }
        onMouseLeave={(e)=> {   
         if(dataC!=props.data) // to avoid call handleupdate each time you leave the textfield

      {            
         props.handlechange(e.target.value)  // update partent.data 

      }
    }
            
        />
        
        </Grid>)
    
    }

exmple parent

export default function Parent(){
const [data,setData]=useState()
return(
 <Grid>
         <Child handlechange={handlechanges} data={data}/>
</Grid>)
}
0
votes

Your form is rerendered when you type in a character, because you have an onChange method, which changes the state. Every state change causes the form to rerender and that is why the input method is loosing focus.

Since you are using redux, the best way would be to store the post title value in the redux object. Also, you may want to have a look at using redux-form for your form.

To get the value of the input without the re-rendering you need to use refs.

0
votes

I am not authorised to comment then it must be an answer. I had similar issue and Answer from Alex Yan was corect.

Namely I had that function

const DisplaySearchArea =()=>{return (arrayOfSearchFieldNames.map((element, index)=>{return(<div key ={index} className = {inputFieldStyle}><input  placeholder= {arrayOfPlaceholders[index]} type="text" className='border-0'
value={this.state[element]}
onChange={e => {this.setState({ [element]: e.target.value }); console.log(e.target)}}
onMouseEnter={e=>e.target.focus()}/></div>)}))}

that behaves OK with FF and not with Chrome when rendered as <DisplaySearchArea /> When render as {...} it's OK with both. That is not so 'beaty' looking code but working, I have already been told to have tendency to overuse lambdas.

0
votes

Thanks, Alex. This way I solved my issue:

constructor(props, context) {
    ...
    this.FormPostSingle = this.FormPostSingle.bind(this);
}
FormPostSingle() {
        const onChange = this.onChange;
        const onSubmit = this.onSubmit;
        const valueTitle = this.state.post.title;
        return (
        <form onSubmit={onSubmit}>
                <InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
                <InputSubmit name="Save" />
            </form>        );
}
render() {
    let FormPostSingle = this.FormPostSingle
    return...
}
0
votes

set the correct id, make sure no other component has same id, set it unique, and it should not change on state update, most common mistake is updating the id with changed value on state update

0
votes

I had this issue, it was being cause by react-bootstrap/Container, once I got rid of it, included a unique key for every form element, everything worked fine.

0
votes

In my case, I had this on a child,

  //in fact is a constant
  const RenderOnDelete=()=>(
  <>  .
      .
      <InputText/>
      .
      .
  </>
)


//is a function that return a constant
const RenderOnRadioSelected=()=>{
  
  switch (selectedRadio) {
    case RADIO_VAL_EXIST:
           return <RenderOnExist/>
    case RADIO_VAL_NEW:
           return <RenderOnNew/>
    case RADIO_VAL_DELETE:
           return <RenderOnDelete/>
    default:
          return <div>Error</div>
  }
}

and this in the parent

return(
<> 
.
<RenderOnRadioSelected/>
.
</>
)

Y solved it by not calling a component but a function() or a constant, depending on the case. . . .

  //in fact is a constant
  const RenderOnDelete=(
  <>  .
      .
      <InputText/>
      .
      .
  </>
)


//is a function that return a constant
const RenderOnRadioSelected=()=>{
  
  switch (selectedRadio) {
    case RADIO_VAL_EXIST:
           return {RenderOnExist}
    case RADIO_VAL_NEW:
           return {RenderOnNew}
    case RADIO_VAL_DELETE:
           return {RenderOnDelete}//Calling the constant
    default:
          return <div>Error</div>
  }
}

and this in the parent

return(
<> 
.
{RenderOnRadioSelected()}//Calling the function but not as a component
.
</>
)
0
votes

For the ones on React Native facing the issue where the text input goes out of focus after typing in single character. try to pass your onChangeText to your TextInput component. eg:

const [value, setValue] = useState("")
const onChangeText = (text) => {
      setValue(text)
}
return <TextInput value={value} onChangeText={onChangeText} />
0
votes

Adding yet another answer: This happened to me when returning a higher order component inside another component. Eg instead of:

/* A function that makes a higher order component */
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
    const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
        const { awesomeThing, ...passThroughProps } = props;    

        return (
            <strong>Look at: {awesomeThing}!</strong>
            <Component {...passThroughProps} />
        );

    }

    return AwesomeComponent;
}

/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {

    const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> = 
        makeMyAwesomeHocComponent(TextInput);

    return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}

Move the call to create the higher order component out of the thing you're rendering.

const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
    const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
        const { awesomeThing, ...passThroughProps } = props;    

        return (
            <strong>Look at: {awesomeThing}!</strong>
            <Component {...passThroughProps} />
        );

    }

    return AwesomeComponent;
}

/* We moved this declaration */
const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> = 
    makeMyAwesomeHocComponent(TextInput);

/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {
    return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}

0
votes

Basically create a ref and assign it to the input element

const inputRef = useRef(null); // Javascript

const inputRef = useRef<HTMLInputElement>(null); // Typescript

// In your Input Element use ref and add autofocus

<input ref={inputRef} autoFocus={inputRef.current === document.activeElement} {...restProps} />

This will keep the input element in focus when typing.

0
votes

Solution for this problem is to use useCallback It is used to memoize functions which means it caches the return value of a function given a set of input parameters.

const InputForm = useCallback(({ label, lablevalue, placeholder, type, value,setValue }) => {
  return (
      <input
        key={label}
        type={type}
        value={value}
        onChange={(e) => setIpValue(e.target.value)}
         placeholder={placeholder}
      />
      );
},[]);

Hope it will solve your problem