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