Controlled component not rerendering/updating on setValue() #13101
-
|
I have created a controlled component to create input masks when using React-Imask. It looks like this: import { InputHTMLAttributes } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { MaskedStyledInput } from "../../theme/inputStyles";
import InputContainer from "./InputContainer";
import InputError from "./InputError";
import InputWrapper from "./InputWrapper";
import Label from "./Label";
interface Props extends InputHTMLAttributes<HTMLInputElement> {
mask: any;
name: string;
label?: string;
}
function MaskedInput({
mask,
name,
label,
disabled,
className,
...rest
}: Props) {
const {
control,
formState: { errors },
} = useFormContext();
const hasError = !!errors[name];
return (
<InputContainer>
<InputWrapper>
<Label name={name}>{label}</Label>
<Controller
control={control}
name={name}
disabled={disabled}
render={({
field: { onChange, value, onBlur, ref, disabled },
}) => (
<MaskedStyledInput
inputRef={ref}
mask={mask}
value={`${value}`}
unmask={"typed"}
id={name}
onBlur={onBlur}
onAccept={(value: string) =>
onChange({
target: { name: name, value: value },
})
}
className={`${className} ${
hasError ? "has-error" : ""
}`}
disabled={disabled}
{...rest}
/>
)}
/>
</InputWrapper>
<InputError name={name} />
</InputContainer>
);
}
export default MaskedInput;I have a particular field that I want to be automatically calculated once the form data rolls in, so in another component I have a watch setup like so: const UnitValue = () => {
const { control, setValue } = useFormContext();
const fields = useWatch({
control,
name: [
"sales",
"use_net_receipts",
"net_receipts",
],
});
const [
sales,
use_net_receipts,
net_receipts,
] = fields;
useEffect(() => {
const unitValue =
sales !== "" && sales !== "0" && net_receipts !== ""
? `${+(net_receipts / sales)}`
: "";
console.log(unitValue);
setValue("unit_value", unitValue);
}, [sales, use_net_receipts, net_receipts]);
return <MaskedInput
mask={[
{
mask: Number,
radix: ".",
mapToRadix: ["."],
scale: 32,
normalizeZeros: false,
thousandsSeparator: ",",
},
]}
name="unit_value"
label="Unit Value"
disabled={!use_net_receipts}
/>;
}However, when the form loads I can see the calculated value in the console, but the value in the field doesn't appear until I change one of the watched fields. Does anyone know why this is happening? Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
What’s happening is that You can fix this by making sure the field is registered before you call Or, if you only want it to appear immediately after mount, you could add a small check like: Basically, the issue isn’t with MaskedInput, it’s just that React Hook Form doesn’t sync the value until the field has been fully mounted. |
Beta Was this translation helpful? Give feedback.
What’s happening is that
setValueruns before theControllerforunit_valuehas registered the field on the first render, so React Hook Form doesn’t know yet that it should update that input. That’s why you see the correct value in the console, but nothing shows up in the field until one of the dependencies changes and triggers a re-render.You can fix this by making sure the field is registered before you call
setValue. The easiest way is to addshouldUnregister: falsein youruseFormsetup, or you can force an update once everything is mounted using something like: