232

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;
spunge
  • 2,827
  • 3
  • 14
  • 12
  • 1
    Thanks for this question, it got linked from https://www.paypal-community.com/t5/Managing-Account/Enter-your-code-page-input-looses-focus/m-p/2876787/highlight/true#M21997 since this might be related to why PayPal's 2FA started loosing focus as well... – Tobias Kienzler Jan 24 '22 at 07:53

33 Answers33

181

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>
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Alex Yan
  • 2,115
  • 1
  • 10
  • 8
  • 4
    Not sure why this was down voted when it's correct. It is the anonymous function FormPostSingle that is causing this problem, because it is declared inside render a new function with a new function signature will be created each time render runs which causes React to lose track of the elements it creates. – f.anderzon Aug 15 '18 at 09:06
  • 5
    so I'm guessing you are correct, but the reason why you can't just put the elements in directly is most likely because the whole reason you are doing this is to get reuse for more than one. what would you do if you wanted multiple `FormPostSingle` components? – Nateous Mar 14 '19 at 14:57
  • 10
    @Nateous What I meant is . "Don't create your form function under render function", If you'd like to reuse a form component, you should create a separate function/component file and use it. – Alex Yan Mar 15 '19 at 18:49
  • @AlexYan thanks, I'm new to React and have been struggling with where to put components. For now I'm trying to just create things as new files/components right from the start and it has solved many issues! (I'll revisit inline later when I have time to dive deeper) – Nateous Mar 16 '19 at 14:49
  • 5
    This answer was helpful I made the thing into a function call like `{FormPostSingle(props)}` and it worked instantly. I don't know what you're talking about with render function because I use hooks – jack blank Jan 05 '20 at 00:00
  • 43
    For function component people, this means don't define components inside of your function component body... essentially every comp will be at the top level of the module (even if you have more than one in a module).... I got caught on this issue using a styled-component definition inside a func. comp. It _looks_ like an expression but of course it is actually calling a function in the weird (to me) styled component template literal syntax. Opps! – Henry Mueller Jan 27 '20 at 20:03
  • Simply remove the components defined inside other components (put them outside the function) like @HenryMueller said – m'hd semps Dec 26 '21 at 12:24
  • 1
    I would like to highlight @jackblank 's comment above where he mentioned `I made the thing into a function call like {FormPostSingle(props)} and it worked instantly`. I was attempting to refactor my code (I have a game board with many inputs on it) and this answer worked instantly as he mentions. Thank you Jack you saved me a BUNCH of time! – Ergin Feb 07 '22 at 02:04
  • I was having the same issue and this answer was what I needed, thanks. – Walter Soto Mar 09 '22 at 00:37
  • Thanks @HenryMueller for your comment. Mine also was caused by an improper use of styled components. – Michael Boñon Jul 07 '22 at 04:18
  • This worked for me too - I had the same issue: losing focus from an input field because the field was nested inside a component. I took the code out of the component and it works fine! – Weetobix Jul 27 '22 at 09:30
  • @HenryMueller You're promoted. – Spencer Williams Jan 09 '23 at 03:44
110

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.

Sandip Mane
  • 1,420
  • 1
  • 9
  • 14
  • 3
    I had a similar situation. I was using an autogenerated key for my input components via nanoid. Each rerender would give it a new key. Changed it to a key that stays the same on rerenders, and it started working -- no more losing focus. – amota Mar 13 '22 at 20:09
  • 2
    I was having extra dynamic Key :-) based on input conditional key - That was my reason – kakabali Jul 18 '22 at 14:11
  • I was setting `key={nanoid()}` directly. To fix it, I just used nanoid() in the new item i was inserting in the list. Thanks for the pointer! – Santhosh Jul 07 '23 at 14:16
73

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})}
);
Sergi Juanati
  • 1,230
  • 1
  • 9
  • 17
  • 10
    How weird and annoying, thanks for the tip, this worked for me too! I was using foo() before, but changed it to . I had to change it back to get my inputs to work again. – DavGarcia Jan 24 '21 at 02:38
  • This change worked for me too. I wonder what is the reason for that? – Normal Feb 09 '21 at 14:16
  • 2
    NOTE that if you do this & you have a state var in `foo`, your parent will re-render EVERY TIME foo's state changes. – Jericho Feb 24 '21 at 14:55
  • 9
    Thank you very much. This drove me crazy, so I had to research why this happens. In case you are still interested, here's an explanation @Normal: [link](https://dev.to/igor_bykov/react-calling-functional-components-as-functions-1d3l) – m_wer May 28 '21 at 09:25
  • Fcking named components which are constantly getting created as new – entropyfeverone Nov 12 '21 at 12:38
54

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

Example:

    <TextField
      key="password" // <= this is the solution to prevent re-render
      label="eMail"
      value={email}
      variant="outlined"
      onChange={(e) => setEmail(e.target.value)}
    />
Chukwuemeka Maduekwe
  • 6,687
  • 5
  • 44
  • 67
Ezra Chang
  • 1,268
  • 9
  • 12
  • What does that mean, 'provide your components keys'? Do you have a link to any documentation? – Sterling Bourne Jun 12 '19 at 18:37
  • 7
    Correct explanation of cause but key suggestion doesn't fix issue. Just fyi for anyone who can't figure out why solution won't work for them. – jvhang Sep 19 '19 at 02:18
  • 11
    For who it may help, this can also be caused by having a key where it is not required. I had key={Math.random()} on the body of a table which then had dynamic rows and this caused this error. – Gavin Mannion Nov 04 '19 at 05:46
  • 5
    Having an ever-changing key attribute provokes that a component is always re-rendered, as opposed to being diffed and re-rendering only the changed parts. – Juan Lanus Jun 13 '20 at 16:18
  • 1
    @GavinMannion That is working for me! Could you please address it in more detail? Why people always have this line of code? – jiadong Sep 22 '21 at 09:13
  • Be careful to keep the same key between render calls if you have it dynamic. It may be obvious but there are some examples that generate ids/keys with random number generators (for table rows for ex.) which break the above solution because the same key is not retained between calls. – Alex P. Jul 08 '22 at 18:06
24

By adding

autoFocus="autoFocus"

in the input worked for me

<input
  type="text"
  autoFocus="autoFocus"
  value = {searchString}
  onChange = {handleChange}
/>
floss
  • 2,603
  • 2
  • 20
  • 37
19

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.

Dave Chong
  • 405
  • 4
  • 4
12

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
Dharman
  • 30,962
  • 25
  • 85
  • 135
Lawrence_NT
  • 438
  • 4
  • 11
  • Just spent ages trying to solve this, dismissed your answer as irrelevant, then realised it is the same as mine!! Thank you!!!!!! – schoon May 11 '21 at 08:05
11

I had a similar issue when using styled-components inside a functional component. The custom input element was losing focus every time I typed a character.

After much searching and experimenting with the code, I found that the styled-components inside the functional component was making the input field re-render every time I typed a character as the template literal syntax made the form a function although it looks like an expression inside Devtools. The comment from @HenryMueller was instrumental in making me think in the right direction.

I moved the styled components outside my functional component, and everything now works fine.

 import React, { useState } from "react";
 import styled from "styled-components";

 const StyledDiv = styled.div`
  margin: 0 auto;
  padding-left: 15px;
  padding-right: 15px;
  width: 100%;
`;

const StyledForm = styled.form`    
  margin: 20px 0 10px;
`;

 const FormInput = styled.input`
  outline: none;
  border: 0;      
  padding: 0 0 15px 0;
  width: 100%;
  height: 50px;
  font-family: inherit;
  font-size: 1.5rem;
  font-weight: 300;
  color: #fff;
  background: transparent;
  -webkit-font-smoothing: antialiased;
`;

const MyForm = () => {
const [value, setValue] = useState<string>("");

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
   setValue(e.target.value);
} 

const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault();
  if(value.trim() === '') {
    return;
  }

  localStorage.setItem(new Date().getTime().toString(), JSON.stringify(value));
  setValue('');
}

 return (
  <StyledDiv>
     <StyledForm onSubmit={handleSubmit}>
        <FormInput type="text" 
        id="inputText" 
        name="inputText"            
        placeholder="What Do You Want To Do Next?"
        value={value}
        onChange={handleChange}/>
    </StyledForm>
   </StyledDiv>
  )
 }

export default MyForm;

The best way to use styled-components in cases like this would be to save them in separate files and import them.

NetYogi
  • 243
  • 5
  • 11
  • 2
    I can't thank you enough. this was my issue having styled component. I solved it by placing the styled components outside my function. I could also put it in separate file. thanks much for posting this. – sandy Feb 18 '22 at 15:43
  • thank you ! was also my problem with dispatching to redux – Harvester Haidar Jul 15 '22 at 16:03
3

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>
Kevin Danikowski
  • 4,620
  • 6
  • 41
  • 75
  • But what do you do if we have 200 components we want to return, but don't want to duplicate the
    code 200 times?
    – Sterling Bourne Jun 12 '19 at 18:29
  • 1
    @SterlingBourne I believe you would want to change the component lifecycle for the parent and maping code, so `{NewSocialPosts.map(SocialPost => )}` kind of thing – Kevin Danikowski Jun 12 '19 at 21:18
3

Solution -

  1. Add a unique key to the input element because it helps React to identify which item has changed(Reconciliation). Ensure that your key should not change, it has to be constant as well as unique.

  2. If you are defining a styled component inside a react component. If your input element is inside that styled component then define that styled component outside the input's component. Otherwise, on each state change of the main component, it will re-render your styled component and input as well and it will lose focus.

import React, { useState } from "react";
import styled from "styled-components";

const Container = styled.div`
  padding: 1rem 0.5rem;
  border: 1px solid #000;
`;

function ExampleComponent() {
  // Container styled component should not be inside this ExampleComponent
  const [userName, setUserName] = useState("");

  const handleInputChange = event => {
    setUserName(event.target.value);
  };

  return (
    <React.Fragment>
      <Container> {/* Styled component */}
        <input
          key="user_name_key" // Unique and constant key
          type="text"
          value={userName}
          onChange={handleInputChange}
        />
      </Container>
    </React.Fragment>
  );
}

export default ExampleComponent;

Sanjib Roy
  • 59
  • 6
2

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!

Nateous
  • 757
  • 9
  • 23
2

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>)
}
ashuvssut
  • 1,725
  • 9
  • 17
Ayoub Benayache
  • 1,046
  • 12
  • 28
2

I was facing the same issue, as soon as I type any character, it was losing focus. adding autoFocus props helped me to resolve this issue.

TypeScript Code Snippet

  • 4
    If you have only one input field in the form, then this is ok. But if you have multiple input filed, then this will not work as per expectation. – Musadul Islam Oct 26 '22 at 06:40
1

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
.
</>
)
Hector Pineda
  • 169
  • 1
  • 2
  • Hi and welcome to stack overflow! If you're having an issue, don't hesitate to [ask new questions](https://stackoverflow.com/help/how-to-ask). – Saud Feb 27 '21 at 06:28
1

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 */ }} />
}

James Paterson
  • 2,652
  • 3
  • 27
  • 40
1

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.

1

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

  • Sadly this won’t make much difference in this situation. For one thing, you’ve forgotten to add the dependant keys to the array, so the value of the input won’t update as they type. Secondly because, if you do add the dependant keys, then the value will change and so the component will be re-rendered despite the `useCallback`. – Brook Jordan Jun 13 '21 at 12:43
1

This is a great question, and I had the same problem which was 3 parts.

  1. RandomGenerated keys.
  2. Wrong event type.
  3. wrong react JSX attribute.

Keys: when you use random keys each rerender causes react to lose focus (key={Math.random()*36.4621596072}).

EventTypes: onChange cause a rerender with each key stroke, but this can also cause problems. onBlur is better because it updates after you click outside the input. An input, unless you want to "bind" it to something on the screen (visual builders), should use the onBlur event.

Attributes: JSX is not HTML and has it's own attributes (className,...). Instead of using value, it is better to use defaultValue={foo} in an input.

once I changes these 3 things it worked great. Example below.

Parent:

const [near, setNear] = useState( "" );
const [location, setLocation] = useState( "" );
 <ExperienceFormWhere 
        slug={slug} 
        questionWhere={question_where} 
        setLocation={handleChangeSetLocation} 
        locationState={location} 
        setNear={setNear} 
        nearState={near} 
        key={36.4621596072}/>

Child:

<input 
defaultValue={locationState} 
className={slug+"_question_where_select search_a_location"} 
onBlur={event => setLocation(event.target.value)}/>
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Peter MAY
  • 21
  • 4
1

If you happen to be developing atomic components for your app's design system, you may run into this issue.

Consider the following Input component:

export const Input = forwardRef(function Input(
  props: InputProps,
  ref: ForwardedRef<HTMLInputElement>,
) {
  const InputElement = () => (
    <input ref={ref} {...props} />
  );

  if (props.icon) {
    return (
      <span className="relative">
        <span className="absolute inset-y-0 left-0 flex items-center pl-2">
          <label htmlFor={props.id} className="p-1 cursor-pointer">
            {icon}
          </label>
        </span>
        <InputElement />
      </span>
    );
  } else {
    return <InputElement />;
  }
});

It might seem like a simple optimization at first to reuse your input element across both branches of your conditional render. However, anytime the parent of this component re-renders, this component re-renders, and when react sees <InputElement /> in the tree, it's going to render a new <input> element too, and thus, the existing one will lose focus.

Your options are

  1. memoize the component using useMemo
  2. duplicate some code and define the <input> element in both branches of the conditional render. in this case, it's okay since the <input> element is relatively simple. more complex components may need option 1

so your code then becomes:

export const Input = forwardRef(function Input(
  props: InputProps,
  ref: ForwardedRef<HTMLInputElement>,
) {
  if (props.icon) {
    return (
      <span className="relative">
        <span className="absolute inset-y-0 left-0 flex items-center pl-2">
          <label htmlFor={props.id} className="p-1 cursor-pointer">
            {icon}
          </label>
        </span>
        <input ref={ref} {...props} />
      </span>
    );
  } else {
    return <input ref={ref} {...props} />;
  }
});
biodynamiccoffee
  • 518
  • 7
  • 12
1

I did the following steps:

  1. Move dynamic component outside a function
  2. Wrap with useMemo function
const getComponent = (step) =>
 dynamic(() => import(`@/components/Forms/Register/Step-${step}`), {
   ssr: false,
 });

And call this function inside the component by wrapping useMemo:

const CurrentStep = useMemo(() => getComponent(currentStep), currentStep]);
Mert
  • 474
  • 2
  • 8
  • 21
1

I'm very late but I have been tracking down this issue for days now and finally fixed it. I hope it helps someone.

I'm using Material-ui's Dialog component, and I wanted the dialog to show when a menu item was clicked. Something like so:

import React, { useState } from "react";
import {
  Menu,
  MenuItem,
  Dialog,
  DialogContent,
  TextField,
} from "@mui/material";

const MyMenu = () => {
  const [open, setOpen] = useState(false);
  return (
    <Menu>
      <MenuItem>option 1</MenuItem>

      <MenuItem onClick={() => setOpen(!open)}>
        option 2
        <Dialog open={open}>
          <DialogContent>
            <TextField />
          </DialogContent>
        </Dialog>
      </MenuItem>
      
    </Menu>
  );
};

I was having issues with the TextField losing focus, but only when hitting the a, s, d, c and v keys. If I hit any one of those keys, it would not type anything in the textfield and just lose focus. My assumption upon fixing the issue was that some of the menu options contained those characters, and it would try to switch focus to one of those options.

The solution I found was to move the dialog outside of the Menu component:

const MyMenu = () => {
  const [open, setOpen] = useState(false);
  return (
      <>
    <Menu>
      <MenuItem>option 1</MenuItem>

      <MenuItem onClick={() => setOpen(!open)}>
        option 2
      </MenuItem>

    </Menu>

    <Dialog open={open}>
      <DialogContent>
        <TextField />
      </DialogContent>
    </Dialog>
      </>
  );
};

I am unable to find anyone with my specific issue online, and this was the post that came up at the top in my searches so I wanted to leave this here. Cheers

Brandons404
  • 109
  • 1
  • 9
0

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

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...
}
Yul S
  • 127
  • 2
  • 5
0

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

Nikhil bhatia
  • 1,297
  • 1
  • 8
  • 9
0

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.

Punter Bad
  • 473
  • 7
  • 14
0

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

I did it with a useRef on input and useEffect

For me this was happening inside Material UI Tabs. I had a search input filter which filtered the table records below it. The search input and table were inside the Tab and whenever a character was typed the input would lose focus (for the obvious reason of re render, the whole stuff inside a tab).

I used the useRef hook for input field ref and then inside my useEffect I triggered the input's focuswhenever the datalist changed. See the code below

const searchInput = useRef();

useEffect(() => {
    searchInput.current.focus();
}, [successfulorderReport]);
Wahab Shah
  • 2,066
  • 1
  • 14
  • 19
0

If working with multiple fields – and they have to be added and removed dynamically for whatever reason – you can use autofocus. You have to keep track of the focus yourself, though. More or less like this:

focusedElement = document.activeElement.id;
[…]

const id = 'dynamicField123'; // dynamically created.
<Input id={id} key={id} {...(focusedElement === id ? { autoFocus: true } : {})} />
WoodrowShigeru
  • 1,418
  • 1
  • 18
  • 25
0

This issue got me for a second. Since I was using Material UI, I tried to customize one of the wrapper components of my form using the styled() API from material UI. The issue was caused due to defining the DOM customization function inside my render function body. When I removed it from the function body, it worked like a charm. So my inspection is, whenever I updated the state, it obviously tried to refresh the DOM tree and redeclare the styled() function which is inside the render body, which gave us a whole new reference to the DOM element for that wrapper, resulting in a loss of focus on that element. This is just my speculation, please enlighten me if I am wrong.

So removing the styled() implementation away from the render function body solved the issue for me.

Anil Rai
  • 104
  • 2
0

This is silly, but... are you (reader, not OP) setting disabled={true} ever?

This is a silly contribution, but I had a problem very much like the one this page is talking about. I had a <textarea> element inside a component that would lose focus when a debounce function concluded.

Well, I realized I was on the wrong track. I was setting the <textarea> to disabled={true} whenever an auto-save function was firing because I didn't want to let the user edit the input while their work was being saved.

When a <textarea> is set to be disabled it will lose focus no matter what trick you try shared here.

I realized there was zero harm in letting the user continue to edit their input while the save was occurring, so I removed it.

Just in case anyone else is doing this same thing, well, that might be your problem. Even a senior engineer with 5 years of React experience can do things that dumb.

KG23
  • 105
  • 7
0

I have just encountered the same issue. What I am trying to accomplish is have a user insert a OTP during a login process but the thing here is I send the PIN to the user via email, move them to the next page and start a countdown using setInterval which was the one causing my issue because it updates this.setState({counter:counter++}) and once that happens the application re-renders and since my form is in the render: function() it gets re-rendered as well and clears the OTP field, having the user needing to restart entering the PIN.

So what I did was to create my input field like this:

<input name="otp" value={this.state.counter} onChange="updateValue(e)"/>

and then have my updateValue function do the update

function updateValue(e){
    this.setState({counter:e.currentTarget.value})
}

Meaning as my timer will continue to reset state and even re-render but because the value on the input field persists, it won't be lost.

I know it may have solved my problem which is a bit different but the logic is basically the same as it solves the same problem.

Relcode
  • 505
  • 1
  • 6
  • 16
0

I am new to react and ran into something similar and just solved it by putting the new component outside the old component. Let me show you how:

Wrong way of building components:

function TodoList() {
  const initialTasks = [
    { id: 0, title: "Make Bed", completed: false },
    { id: 1, title: "Brush Teeth", completed: false },
    { id: 2, title: "Workout", completed: false },
    { id: 3, title: "Plan your day", completed: true },
    { id: 4, title: "Brew coffee", completed: false },
    { id: 5, title: "Login to work", completed: false },
  ];
  const [taskList, setTaskList] = useState(initialTasks);

function AddTaskForm({ taskList, onShow }) {
  const [newTask, setNewTask] = useState("");

  function handleAddTask(event) {
    event.preventDefault();

    var newList = taskList.map((task) => {
      return task;
    });
    if (newTask !== "") {
      var temp = { id: taskList.length + 1, title: newTask, completed: false };
      newList.push(temp);
      onShow(newList);
    }
  }

  return (
    <>
      <form onSubmit={handleAddTask}>
        <input
          name="addtask"
          type="text"
          className="form-control"
          id="addtask"
          aria-describedby="addtask"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <br />
        <button type="submit" className="btn btn-primary">
          Submit
        </button>
      </form>
    </>
  );
}

return (
    <div className="container">
      <AddTaskForm key="random1" taskList={taskList} onShow={setTaskList} />
      <h2>Incomplete Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={false} />
        ))}
      </ul>
      <br />
      <h2>Completed Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={true} />
        ))}
      </ul>
      <br />
    </div>
  );
}

if you notice in the above code I have added AddTaskForm component INSIDE the TodoList component which is a no no in react I found, so the right way of doing the same thing is this:

function AddTaskForm({ taskList, onShow }) {
  const [newTask, setNewTask] = useState("");

  function handleAddTask(event) {
    event.preventDefault();

    var newList = taskList.map((task) => {
      return task;
    });
    if (newTask !== "") {
      var temp = { id: taskList.length + 1, title: newTask, completed: false };
      newList.push(temp);
      onShow(newList);
    }
  }

  return (
    <>
      <form onSubmit={handleAddTask}>
        <input
          name="addtask"
          type="text"
          className="form-control"
          id="addtask"
          aria-describedby="addtask"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <br />
        <button type="submit" className="btn btn-primary">
          Submit
        </button>
      </form>
    </>
  );
}

function TodoList() {
  const initialTasks = [
    { id: 0, title: "Make Bed", completed: false },
    { id: 1, title: "Brush Teeth", completed: false },
    { id: 2, title: "Workout", completed: false },
    { id: 3, title: "Plan your day", completed: true },
    { id: 4, title: "Brew coffee", completed: false },
    { id: 5, title: "Login to work", completed: false },
  ];
  const [taskList, setTaskList] = useState(initialTasks);

  function handleClick(id) {
    var newList = taskList.map((task) => {
      if (task.id === id) {
        task.completed = !task.completed;
      }

      return task;
    });

    setTaskList(newList);
    console.log(newList);
  }

  return (
    <div className="container">
      <AddTaskForm key="random1" taskList={taskList} onShow={setTaskList} />
      <h2>Incomplete Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={false} />
        ))}
      </ul>
      <br />
      <h2>Completed Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={true} />
        ))}
      </ul>
      <br />
    </div>
  );
}

Remember keep components separate and then it won't lose focus from the input field. Hope this helps someone, edit it if I missed something.

Ali
  • 33
  • 5
0

I faced this issue today, I know the reason behind it and for anyone here I recommend using and onSubmit() as suggested above. However, in my case it would be an overkill so I needed an easy solution. I decided to use references

const commentRef = React.useRef<HTMLTextAreaElement>(null);

and then in render

<Textarea ref={commentRef} defaultValue = {someobj.comment} />

to get value during an event I used

commentRef?.current?.value

it works. Although, if you can use the solution with form submit which is cleaner.

Robert
  • 19,800
  • 5
  • 55
  • 85