Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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>
  );
}
like image 508
Dragos Strugar Avatar asked Jan 13 '20 10:01

Dragos Strugar


People also ask

Do React Hooks work with static typing?

Do Hooks work with static typing? Hooks were designed with static typing in mind. Because they're functions, they are easier to type correctly than patterns like higher-order components. The latest Flow and TypeScript React definitions include support for React Hooks.

Can React Hooks be called conditionally?

The error "React hook 'useState' is called conditionally" occurs when we use the useState hook conditionally or after a condition that may return a value. To solve the error, move all React hooks above any conditionals that may return a value.

Is React hook form uncontrolled?

React Hook Form relies on uncontrolled form, which is the reason why the register function capture ref and controlled component has its re-rendering scope with Controller or useController .


3 Answers

While 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 agains 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

like image 64
Kasparas Anusauskas Avatar answered Sep 29 '22 02:09

Kasparas Anusauskas


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
/>
like image 29
Kais Avatar answered Sep 29 '22 02:09

Kais


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.

like image 40
codeinaire Avatar answered Sep 29 '22 04:09

codeinaire