How to set focus a ref using React-Hook-Form - reactjs

How do you implement set focus in an input using React-Hook-Form, this is what their FAQ's "How to share ref usage" code here https://www.react-hook-form.com/faqs/#Howtosharerefusage
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const firstNameRef = useRef();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="firstName" ref={(e) => {
register(e)
firstNameRef.current = e // you can still assign to ref
}} />
<input name="lastName" ref={(e) => {
// register's first argument is ref, and second is validation rules
register(e, { required: true })
}} />
<button>Submit</button>
</form>
);
}
I tried set focusing the ref inside useEffect but it doesn't work:
useEffect(()=>{
firstNameRef.current.focus();
},[])
Neither does inside the input:
<input name="firstName" ref={(e) => {
register(e)
firstNameRef.current = e;
e.focus();
}} />

You can set the focus using the setFocus helper returned by the useForm hook (no need to use a custom ref):
const allMethods = useForm();
const { setFocus } = allMethods;
...
setFocus('inputName');
https://react-hook-form.com/api/useform/setFocus

Are you using Typescript?
If so, replace...
const firstNameRef = useRef();
With...
const firstNameRef = useRef<HTMLInputElement | null>(null);

useEffect(() => {
if (firstNameRef.current) {
register(firstNameRef.current)
firstNameRef.current.focus()
}
}, []);
<input name="firstName" ref={firstNameRef} />
Got this from : https://github.com/react-hook-form/react-hook-form/issues/230

If you using Version 7, you can check this link in the docs
https://www.react-hook-form.com/faqs/#Howtosharerefusage

I can't comment (I don't have enough reputation), but without setting a timeout, setFocus didn't work. After adding a simple timeout from Subham's answer, it worked like a charm!
PS: Even adding a timeout of 0: setTimeout(() => setFocus(fieldName), 0) works. Can anyone explain?

I think you can simply use ref={(el) => el.focus()} to set the focus. The catch here is to make sure no other element within your page is also setting focus right after that el.focus() call.

const {
register,
handleSubmit,
setFocus, // here
formState: { errors },
} = useForm<FormFields>({
resolver: yupResolver(LoginSchema),
mode: "onTouched",
});
useEffect(() => {
setFocus("email");
}, [setFocus]);

Related

React Hook Form and persistent data on page reload

I'm using React Hook Form v7 and I'm trying to make my data form persistent on page reload. I read the official RHF documentation which suggests to use little state machine and I tried to implement it but without success. Is there a better way to do it? However...
The first problem I encountered using it, is that my data is a complex object so the updateAction it should be not that easy.
The second problem is that I don't know when and how to trigger the updateAction to save the data. Should I trigger it on input blur? On input change?
Here's my test code:
If persisting in the localStorage works you, here is how I achieved it.
Define a custom hook to for persisting the data
export const usePersistForm = ({
value,
localStorageKey,
}) => {
useEffect(() => {
localStorage.setItem(localStorageKey, JSON.stringify(value));
}, [value, localStorageKey]);
return;
};
Just use it in the form component
const FORM_DATA_KEY = "app_form_local_data";
export const AppForm = ({
initialValues,
handleFormSubmit,
}) => {
// useCallback may not be needed, you can use a function
// This was to improve performance since i was using modals
const getSavedData = useCallback(() => {
let data = localStorage.getItem(FORM_DATA_KEY);
if (data) {
// Parse it to a javaScript object
try {
data = JSON.parse(data);
} catch (err) {
console.log(err);
}
return data;
}
return initialValues;
}, [initialValues]);
const {
handleSubmit,
register,
getValues,
formState: { errors },
} = useForm({ defaultValues: getSavedData() });
const onSubmit: SubmitHandler = (data) => {
// Clear from localStorage on submit
// if this doesn’t work for you, you can use setTimeout
// Better still you can clear on successful submission
localStorage.removeItem(FORM_DATA_KEY);
handleFormSubmit(data);
};
// getValues periodically retrieves the form data
usePersistForm({ value: getValues(), localStorageKey: FORM_DATA_KEY });
return (
<form onSubmit={handleSubmit(onSubmit)}>
...
</form>
)
}
I already faced this issue and implemented it by creating a custom Hook called useLocalStorage. But since you are using the React hook form, it makes the code a bit complicated and not much clean!
I suggest you simply use the light package react-hook-form-persist.
The only work you need to do is to add useFormPersist hook after useForm hook. Done!
import { useForm } from "react-hook-form";
import useFormPersist from "react-hook-form-persist";
const yourComponent = () => {
const {
register,
control,
watch,
setValue,
handleSubmit,
reset
} = useForm({
defaultValues: initialValues
});
useFormPersist("form-name", { watch, setValue });
return (
<TextField
title='title'
type="text"
label='label'
{...register("input-field-name")}
/>
...
);
}
The state itself won't persist any data on page reload.
You need to add your state data to Local Storage.
Then load it back into the state on componentDidMount (useEffect with empty dependency array).
const Form = () => {
const [formData, setFormData] = useState({})
useEffect(() => {
if(localStorage) {
const formDataFromLocalStorage = localStorage.getItem('formData');
if(formDataFromLocalStorage) {
const formDataCopy = JSON.parse(formDataFromLocalStorage)
setFormData({...formDataCopy})
}
}
}, []);
useEffect(() => {
localStorage && localStorage.setItem("formData", JSON.stringify(formData))
}, [formData]);
const handleInputsChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
return (
<div>
<input
type="text"
name="firstName"
placeholder='first name'
onChange={e => handleInputsChange(e)}
value={formData?.firstName}
/>
<input
type="text"
name="lastName"
placeholder='last name'
onChange={e => handleInputsChange(e)}
value={formData?.lastName}
/>
</div>
)
}

react-hook-form conditional form value control

I want to delete model's value when manufacturerWatchValue changes after form is initialized.
But model loses its value upon form-initialized since initialized variable is in the dependency list.
Hence, I had to remove it from the dependency list to make it work.
Even though it works as expected, React says Line 138:5: React Hook useEffect has a missing dependency: 'initialized'. Either include it or remove the dependency array
Is there a way to bypass this error while achieving the logic?
const MyForm = ({ itemId }) => {
const { watch, setValue, handleSubmit, reset } = useForm()
const [initialized, setInitialized] = useState(false)
const manufacturerWatchValue = watch("manufacturer")
useEffect(() => {
// Initializing Form Here
itemId &&
(async () => {
const oneItem = await getOneResult(itemId)
reset(oneItem)
setInitialized(true)
})()
// Terminating Form Here
return () => {
reset({})
setInitialized(false)
}
}, [dispatch, reset, itemId])
useEffect(() => {
if (initialized) {
setValue("model", "")
}
}, [manufacturerWatchValue, setValue /*, initialized */])
const onSubmit = (data) => {
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" name="model" ref={register} />
<input type="text" name="manufacturer" ref={register} />
</form>
)
React is not actually throwing an error at you, instead throwing an eslint warning at you.
You can ignore the specific linting error by adding this line above the dependency error:
// eslint-disable-next-line
Example
useEffect(() => {
console.log("My code")
// eslint-disable-next-line
}, [])

What is the reason for useState() react hooks always returns the initial state?

I am making a form in react with validation. I have to show the error after validation. So I used useState() hook as follows. But it seems i can't update the new state as it always returns the initial value. Where did I go wrong?
import React, { useState } from 'react';
import './index.css';
const Firstname = ({ handleInputChange, firstName }) => {
const [error, setError] = useState('hi');
const handleBlur = (event) => {
const { value } = event.target;
validateFirstName(value);
}
const validateFirstName = (name) => {
if (name.trim().length === 0) {
setError('Enter first Name');
}
console.log(error);
}
return (
<>
<label>First Name</label>
<input type='text'
placeholder='Eddard'
name='firstName'
value={firstName}
onChange={handleInputChange}
onBlur={handleBlur} />
</>
)
}
export default Firstname;
I tried to console.log(error) but it always returns the initial state hi.
This is because state updates are asynchronous.
You need to console.log(error) just before your return to see it at each render.
In the input, you should use defaultValue instead of value
<input type='text'
placeholder='Eddard'
name='firstName'
defaultValue={firstName}
onChange={handleInputChange}
onBlur={handleBlur} />
It would also be better if you move the log inside the if-statement
const validateFirstName = (name) => {
if (name.trim().length === 0) {
setError('Enter first Name');
console.log(error);
}
}
setState is asynchronous, if you need to catch the new value you can use useEffect.

Setting value and clicking a submit button with react-testing-library

I have a test that unfortunately reveals some serious misunderstanding on my part on how to test this React web application using react-testing-library. The test:
const setup = () => {
const utils = render(<UnderConstruction />)
const input = utils.getByLabelText('Email')
return {
input,
...utils,
}
}
test('It should set the email input', () => {
const { input } = setup();
const element = input as HTMLInputElement;
fireEvent.change(element, { target: { value: 'abc#def' } });
console.log(element.value);
expect(element.value).toBe('abc#def');
})
The simple component (uses marterial-ui) looks like (I have removed large portions of it for brevity):
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
id="email-notification"
name="email-notification"
label="Email"
value={email}
onInput={(e: React.FormEvent<EventTarget>) => {
let target = e.target as HTMLInputElement;
setEmail(target.value);
}}
placeholder="Email" />
<Button
type="submit"
variant="contained"
color="secondary"
>
Submit
</Button>
</form>
First the as cast is to keep TypeScript happy. Second is mainly around my use of fireEvent to simulate a user input. Any ideas on how I can test this functionality? Righ now the test aways fails as it is expecting abc#def and receiving ''.
Because you are using TextField component of MUI Component, so you can not get real input with getByLabelText. Instead of that, you should use getByPlaceholderText.
Try this:
const setup = () => {
const utils = render(<UnderConstruction />)
const input = utils.getByPlaceholderText('Email')
return {
input,
...utils,
}
}
test('It should set the email input', () => {
const { input } = setup();
const element = input as HTMLInputElement;
fireEvent.change(element, { target: { value: 'abc#def' } });
console.log(element.value);
expect(element.value).toBe('abc#def');
})
Using UI library like this, if getByLabelText or getByPlaceholderText don't work, I usually get the underlying input element this way:
// add a data-testid="myElement" on the TextField component
// in test:
fireEvent.change(getByTestId('myElement').querySelector('input'), { target: { value: 'abc#def' } });
This is not ideal though, getByLabelText or getByPlaceholderText should be used when possible.

React hook not setting the state

Hey all trying to use a useState react hook to set a state but it does not work, I gone through the official documentation
Seems like i have followed it correctly but still cannot get the hook to set the state:
const [search, setSearch] = useState('');
const { films } = props;
const matchMovieSearch = (films) => {
return films.forEach(item => {
return item.find(({ title }) => title === search);
});
}
const handleSearch = (e) => {
setSearch(e.target.value);
matchMovieSearch(films);
}
<Form.Control
type="text"
placeholder="Search Film"
onChange={(e) => {handleSearch(e)}}
/>
Search var in useState is allways empty even when i debug and can see that e.target.value has to correct data inputed from the html field
setSearch is an async call, you won't be able to get the search immediately after setting the state.
useEffect is here for rescue.
useEffect(() => {
// your action
}, [search]);
Are you sure you are using the hooks inside a component, hooks can only be used in a Functional React Component.
If that is not the case, there must be something wrong with the Form.Control component, possibly like that component did not implement the onChanged parameter properly.
This is the one I tested with the html input element, and it is working fine. I used the useEffect hook to track the changes on the search variable, and the you can see that the variable is being properly updated.
https://codesandbox.io/s/bitter-browser-c4nrg
export default function App() {
const [search, setSearch] = useState("");
useEffect(() => {
console.log(`search was changed to ${search}`);
}, [search]);
const handleSearch = e => {
setSearch(e.target.value);
};
return (
<input
type="text"
onChange={e => {
handleSearch(e);
}}
/>
);
}

Resources