React Hooks - Input loses focus when 1 character is typed in
Asked Answered
T

8

52

I'm playing with React Hooks - rewriting a form to use hook concepts. Everything works as expected except that once I type any 1 character into the input, the input loses focus.

I guess there is a problem that the outside of the component doesn't know about the internal changes in the component, but how do I resolve this issue?

Here is the useForm Hook:

import React, { useState } from "react";

export default function useForm(defaultState, label) {
  const [state, setState] = useState(defaultState);

  const FormComponent = () => (
    <form>
      <label htmlFor={label}>
        {label}
        <input
          type="text"
          id={label}
          value={state}
          placeholder={label}
          onChange={e => setState(e.target.value)}
        />
      </label>
    </form>
  );

  return [state, FormComponent, setState];
}

Here is the component that uses the Hook:

function App() {
  const [formValue, Form, setFormValue] = useForm("San Francisco, CA", "Location");

  return (
    <Fragment>
      <h1>{formValue}</h1>
      <Form />
    </Fragment>
  );
}
Tunable answered 13/1, 2020 at 10:50 Comment(1)
Much appreciated that you asked this question!Ramtil
S
64

While the answer by Kais will solve the symptoms, it will leave the cause unaddressed. It will also fail if there are multiple inputs - which one should autofocus itself on rerender then?

The issue happens when you define a component (FormComponent) inside the scope of another function which is called each render of your App component. This gives you a completely new FormComponent each time your App component is rerendered and calls useState. That new component is then, well, without focus.

Personally I would feel against returning components from a hook. I would instead define a FormComponent component, and only return state from useForm state.

But, a working example closest to your original code could be:

// useForm.js
import React, { useState } from "react";

// Define the FormComponent outside of your useForm hook
const FormComponent = ({ setState, state, label }) => (
  <form>
    <label htmlFor={label}>
      {label}
      <input
        type="text"
        id={label}
        value={state}
        placeholder={label}
        onChange={e => setState(e.target.value)}
      />
    </label>
  </form>
);

export default function useForm(defaultState, label) {
  const [state, setState] = useState(defaultState);

  return [
    state,
    <FormComponent state={state} setState={setState} label={label} />,
    setState
  ];
}
// App.js
import useForm from "./useForm";

export default function App() {
  const [formValue, Form] = useForm("San Francisco, CA", "Location");

  return (
    <>
      <h1>{formValue}</h1>
      {Form}
    </>
  );
}

Here's a sandbox

Sewellel answered 3/3, 2020 at 15:10 Comment(7)
This should be the answer.Epistaxis
This really works! But I don't understand why. Please, can you explain why your solution is working?Spermine
@AlfonsoTienda It works because I moved the FormComponent outside of the useForm function, so it always points to the same object (contrary to a new object being created each call of useForm function). As a result, React no longer determines the whole DOM representation of FormComponent needs to be completely redrawn (object is same, no changes except value of input). Then, a flag "isFocused" or similar is no longer lost during the redraw. Remember, each time user inputs something, useForm is called by React with the updated state. At least thats how I understand it.Sewellel
that's mind-blowing, tks. i have a similar issue, but I want to design the form with multiple inputs, so compose it with children inside other page componentes. is possible to pull something like that too?Agincourt
@KasparasAnusauskas I wanted to test something cause I have also issue when change something in Parent, my child app lose focus on input filed. I created working example, Parent render children and children can change state of parent. I expected when I set parent state from children it will cause parent to re-render child component and to lose focus at that moment. But I didn't lose focus in child component. Why that happened? Working example: codesandbox.io/s/parent-child-do-not-lose-focus-4f9cvInnis
@PredragDavidovic your use case is OK (and actually how it's done, mostly) because you do not create the input / form component in your parent. So rerender doesn't create a new input / form component instance on each render. Here, I broke your example so you can see which the issue was in the original question (kind of, as it used a hook but core problem was the same): codesandbox.io/s/parent-child-do-not-lose-focus-forked-nqeqiSewellel
Hey, I understand what is problem, also fixed it in my app. What is intresting is if you reproduce this issues in codesandbox it will work perfectly but when you run it locally it will lose focus. THanks @KasparasAnusauskasInnis
P
28

When you enter any text in input box. Parent Component is also re-rendering. So you need to make focus on input manually. For this, use autoFocus in input tag

<input
  type="text"
  id={label}
  value={state}
  placeholder={label}
  onChange={e => setState(e.target.value)}
  autoFocus
/>
Pet answered 13/1, 2020 at 10:58 Comment(4)
This is the best answer so far, in my case I haven't any parent rendering child component!Duplessis
autoFocus I can't believed this worked.. Still get an controlled react error thoughIlo
This doesn't work for me. It still blurs after first characterWeekender
This does not work with multiple inputs in a form that are having the unfocus issue.Tweezers
R
14

The above answers didn't work for me. The solution that worked for me was much simpler and, for that reason, less obvious.

The Problem Essentially, the value that I was changing with the input was also being used for each key in a list of inputs.

Hence, when I updated the value the key would change and React would detect that it's different relative to the last key and create a new input in its place. As a new input it wouldn't focus on itself.

However, by using autoFocus it would automatically focus on the newly created input. But the issue wasn't fixed as it was obvious that the input was continually going through a cycle of un-focus and focus.

Here's an article demonstrating the problem.

The Fix

Update the key to an unchangeable value so React would have a stable reference to the list items. In my case I just updated it to the index. This is not ideal (React docs recommend using a stable ID), but in my situation it was okay because the order of the items won't change.

Retrench answered 20/7, 2021 at 3:11 Comment(4)
Thanks to this solution, I started thinking out of the box. Another problem that can manifest this way is senseless re-rendering by a parent component. It might not hurt to console.log down your component tree and make sure your re-rendering is only happening when you expect it to! I was fighting the problem at the wrong "level" of the component tree.Rhesus
Thank you for this. Had to read through five identical SO questions before I found your answer, which solved the issue for me.Denationalize
This solution helped me to find my problem after many hours and upvoted. I was using nanoid() id generator as a key. After changing it to unique id problem was solved.Stratagem
this is indeed the most ideal solution for, at least, a subset of users facing this specific issue - if you are changing your input key within itself. Thanks very much!Athwart
G
2

I had this problem when working with styled-components, the problem is that styled components change class every time a change on the input is made, and the class change make the component to re-render.

the solution is to declare the styled component outside the function component like this.

const FormButton = styled.button``

export default function Button(){
   return(   
      <FormButton>Log In</FormButton>        
)
Grocer answered 20/5, 2023 at 20:54 Comment(0)
G
0

The first solution actually worked for me , initially the functional component which contained the text field was part of the main functional component , i was facing the same issue but when i extracted the text-field component into another page and imported it it worked fine This was my component

 <Paper >
      <div>
      <div style={{padding:'10px' ,display:'flex'}}  >       
      <inputstyle={{marginRight:'5px'}} value={val} onChange={(e)=>{setVal(e.target.value)}} />             
    </div>
    </div>
    </Paper>
Goldman answered 23/7, 2020 at 4:7 Comment(0)
B
0

I had a similar problem with multiple inputs(re-useable custom components), I just simply added key to the input and it resolved my issue. This will solve your problem even with multiple inputs. (This was happening due to re-renders of parent component because when react re-renders it doesn't know which input had focus because it didn't had the key from those inputs to identify which one had the focus.) please don't never ever add rand() function as a key id(for newbie's)

**e.g Focues error code due to wrong key props **

return(
 <CustomInput key={rand()} placeHolder="email" type="email" data= 
 {onChangeHandler}/>
 <CustomInput key={rand()} placeHolder="password" type="password" 
 data= 
 {onChangeHandler}/>
 <CustomInput key={rand()} placeHolder="name" type="name" data= 
 {onChangeHandler}/>
 <CustomInput key={rand()} placeHolder="age" type="age" data= 
 {onChangeHandler}/>
 )

Explination of above code .The child component (Custom input component had a useState and when ever a user entered in the input the on change handler use to execute on every single key stroke which caused the parent component to re-render and React get confused after re-rendering becasue of the wrong key props(rand() produced new key props which were differend from previous key prop which made react confuse and thus the focus goes out)

Bellow is the correct code

return(
 <CustomInput key="id1" placeHolder="email" type="email" data= 
 {onChangeHandler}/>
 <CustomInput key="id2" placeHolder="password" type="password" 
 data= 
 {onChangeHandler}/>
 <CustomInput key="id3" placeHolder="name" type="name" data= 
 {onChangeHandler}/>
 <CustomInput key="id4" placeHolder="age" type="age" data= 
 {onChangeHandler}/>
 )

Explanation of above code Now after changing the key to a fixed id, React will know to put back focus on the correct input after the re-render of the parent computer because the key prop(e.g "id1") will be same after the re-render

Beeswax answered 9/8, 2023 at 15:8 Comment(3)
Welcome to Stack overflow :) Just letting you know that giving an example or links to examples would have greatly increased the readability of this answer.Pendentive
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Mcgill
@Pendentive Please review now.Beeswax
L
0

You can use Controller from react-hook-forms, render the element and pass value,onChange and ref on it.

<Controller
        control={control}
        rules={{
            maxLength: 100,
        }}
        render={({ field: { onChange, onBlur, value, ref } }) => (
            <TextInput
                ref={ref}
                placeholder="Last name"
                onBlur={onBlur}
                onChangeText={onChange}
                value={value}
            />
        )}
        name="lastName"
    />
Littrell answered 15/11, 2023 at 8:32 Comment(0)
E
0

Another possible cause of the problem is that you're disabling the input fields during the form validate process onChange.

When the input field is disabled it will lost focus.

In that case it would mean the reason your input loses focus is caused by this process:

  1. Focus on field
  2. Start typing
  3. After the first character is type, the validation process occurs
  4. IF you're currently disabling the fields while validating that disable process will disable all your input
  5. Focus will be lost on your input because it was disable.

So make sure you're not disabling your input for any reason - even if it's just for a split second - because that will cause it to lose focus.

Eade answered 19/11, 2023 at 5:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.