admin管理员组文章数量:1026989
I'm experiencing a challenge with React Hook Form where my child component InputX
re-renders every time there's an update in the parent component or other parts of the application, despite no changes in InputX
itself. I’ve pinpointed that the re-renders are triggered by the use of useFormContext
to access the register
function within InputX
.
Here's a brief outline of my setup:
- The
InputX
component utilizesuseFormContext
specifically for theregister
function. - The form is managed by a
FormProvider
in the parent component.
I've noticed that when I remove useFormContext
from InputX
, the unnecessary re-renders stop. This leads me to believe that something about how useFormContext
is interacting with register
or the context setup might be causing these updates.
import { memo, useRef, useMemo, useEffect } from "react";
import {
useFormContext,
useWatch,
useForm,
FormProvider,
} from "react-hook-form";
import {
MergeFormProvider,
useMergeForm,
useMergeFormUtils,
} from "./MergeFormProvider";
function InputX() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const x = useWatch({ name: "x", control });
renderCount.current += 1;
console.log("Render count InputX", renderCount.current);
const someCalculator = useMemo(() => x.repeat(3), [x]);
return (
<fieldset className="grid border p-4">
<legend>Input X Some calculator {someCalculator}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("x")} placeholder="Input X" />
</fieldset>
);
}
function InputY() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const y = useWatch({ name: "y", control });
renderCount.current += 1;
return (
<fieldset className="grid border p-4">
<legend>Input Y {y}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("y")} placeholder="Input Y" />
</fieldset>
);
}
function TodoByFormID() {
const { formID } = useMergeForm();
/**
* Handle component by form id
*/
return <div></div>;
}
const MemoInputX = memo(InputX);
const MemoInputY = memo(InputY);
function MainForm({ form }) {
const { setFieldOptions } = useMergeFormUtils();
const renderCount = useRef(0);
renderCount.current += 1;
const methods = useForm({
defaultValues: form,
});
const [y, z] = useWatch({
control: methods.control,
name: ["y", "z"],
});
const fieldOptions = useMemo<ISelect[]>(() => {
if (y.length) {
return Array.from({ length: y.length }, (_, index) => ({
label: index.toString(),
value: index + ". Item",
}));
}
return [];
}, [y]);
useEffect(() => {
setFieldOptions(fieldOptions);
}, [fieldOptions]);
return (
<FormProvider {...methods}>
<fieldset>
<legend>Main Form Y Value:</legend>
{y}
</fieldset>
<MemoInputX />
<MemoInputY />
<fieldset className="grid border p-4">
<legend>Input Z {z}</legend>
<div>Render count: {renderCount.current}</div>
<input {...methods.register("z")} placeholder="Input Z" />
</fieldset>
<TodoByFormID />
</FormProvider>
);
}
export default function App() {
const formID = 1;
const form = {
count: [],
x: "",
y: "",
z: "",
};
return (
<MergeFormProvider initialFormID={formID}>
<MainForm form={form} />
</MergeFormProvider>
);
}
For a clearer picture, I’ve set up a simplified version of the problem in this CodeSandbox:
Could anyone explain why useFormContext
is causing these re-renders and suggest a way to prevent them without removing useFormContext
? Any advice on optimizing this setup would be greatly appreciated!
I utilized useFormContext
in InputX
to simplify form handling and avoid prop drilling across multiple component layers in my larger project.
I expected that InputX
would only re-render when its specific data or relevant form state changes (like its own input data).
Note:
- The CodeSandbox link provided is a minimized version of my project. In the full project, I have components several layers deep (grand grand children).
I'm experiencing a challenge with React Hook Form where my child component InputX
re-renders every time there's an update in the parent component or other parts of the application, despite no changes in InputX
itself. I’ve pinpointed that the re-renders are triggered by the use of useFormContext
to access the register
function within InputX
.
Here's a brief outline of my setup:
- The
InputX
component utilizesuseFormContext
specifically for theregister
function. - The form is managed by a
FormProvider
in the parent component.
I've noticed that when I remove useFormContext
from InputX
, the unnecessary re-renders stop. This leads me to believe that something about how useFormContext
is interacting with register
or the context setup might be causing these updates.
import { memo, useRef, useMemo, useEffect } from "react";
import {
useFormContext,
useWatch,
useForm,
FormProvider,
} from "react-hook-form";
import {
MergeFormProvider,
useMergeForm,
useMergeFormUtils,
} from "./MergeFormProvider";
function InputX() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const x = useWatch({ name: "x", control });
renderCount.current += 1;
console.log("Render count InputX", renderCount.current);
const someCalculator = useMemo(() => x.repeat(3), [x]);
return (
<fieldset className="grid border p-4">
<legend>Input X Some calculator {someCalculator}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("x")} placeholder="Input X" />
</fieldset>
);
}
function InputY() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const y = useWatch({ name: "y", control });
renderCount.current += 1;
return (
<fieldset className="grid border p-4">
<legend>Input Y {y}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("y")} placeholder="Input Y" />
</fieldset>
);
}
function TodoByFormID() {
const { formID } = useMergeForm();
/**
* Handle component by form id
*/
return <div></div>;
}
const MemoInputX = memo(InputX);
const MemoInputY = memo(InputY);
function MainForm({ form }) {
const { setFieldOptions } = useMergeFormUtils();
const renderCount = useRef(0);
renderCount.current += 1;
const methods = useForm({
defaultValues: form,
});
const [y, z] = useWatch({
control: methods.control,
name: ["y", "z"],
});
const fieldOptions = useMemo<ISelect[]>(() => {
if (y.length) {
return Array.from({ length: y.length }, (_, index) => ({
label: index.toString(),
value: index + ". Item",
}));
}
return [];
}, [y]);
useEffect(() => {
setFieldOptions(fieldOptions);
}, [fieldOptions]);
return (
<FormProvider {...methods}>
<fieldset>
<legend>Main Form Y Value:</legend>
{y}
</fieldset>
<MemoInputX />
<MemoInputY />
<fieldset className="grid border p-4">
<legend>Input Z {z}</legend>
<div>Render count: {renderCount.current}</div>
<input {...methods.register("z")} placeholder="Input Z" />
</fieldset>
<TodoByFormID />
</FormProvider>
);
}
export default function App() {
const formID = 1;
const form = {
count: [],
x: "",
y: "",
z: "",
};
return (
<MergeFormProvider initialFormID={formID}>
<MainForm form={form} />
</MergeFormProvider>
);
}
For a clearer picture, I’ve set up a simplified version of the problem in this CodeSandbox: https://codesandbox.io/p/sandbox/39397x
Could anyone explain why useFormContext
is causing these re-renders and suggest a way to prevent them without removing useFormContext
? Any advice on optimizing this setup would be greatly appreciated!
I utilized useFormContext
in InputX
to simplify form handling and avoid prop drilling across multiple component layers in my larger project.
I expected that InputX
would only re-render when its specific data or relevant form state changes (like its own input data).
Note:
- The CodeSandbox link provided is a minimized version of my project. In the full project, I have components several layers deep (grand grand children).
2 Answers
Reset to default 2Could anyone explain why
useFormContext
is causing these re-renders and suggest a way to prevent them without removinguseFormContext
?
The useFormContext
hook is not causing extra component rerenders. Note that your InputX
and InputY
components have nearly identical implementations*:
function InputX() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const x = useWatch({ name: "x", control });
renderCount.current += 1;
console.log("Render count InputX", renderCount.current);
const someCalculator = useMemo(() => x.repeat(3), [x]); // *
return (
<fieldset className="grid border p-4">
<legend>Input X Some calculator {someCalculator}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("x")} placeholder="Input X" />
</fieldset>
);
}
function InputY() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const y = useWatch({ name: "y", control });
renderCount.current += 1;
return (
<fieldset className="grid border p-4">
<legend>Input Y {y}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("y")} placeholder="Input Y" />
</fieldset>
);
}
* The difference being that InputX
has an additional someCalculator
value it is rendering.
and yet it's only when you edit inputs Y and Z that trigger X to render more often, but when you edit input X, only X re-renders.
This is caused by the parent MainForm
component subscribing, i.e. useWatch
, to changes to the y
and z
form states, and not x
.
const [y, z] = useWatch({
control: methods.control,
name: ["y", "z"],
});
- When the
y
andz
form states are updated, this triggersMainForm
to rerender, which re-renders itself and its entire sub-ReactTree, e.g. its children. This meansMainForm
,MemoInputX
,MemoInputY
, the "input Z" and all the rest of the returned JSX all rerender. - When the
x
form state is updated, only the locally subscribedInputX
(MemoInputX
) component is triggered to rerender.
If you updated MainForm
to also subscribe to x
form state changes then you will see nearly identical rendering results and counts across all three X, Y, and Z inputs.
const [x, y, z] = useWatch({
control: methods.control,
name: ["x", "y", "z"],
});
I expected that
InputX
would only re-render when its specific data or relevant form state changes (like its own input data).
React components render for one of two reasons:
- Their
state
orprops
value updated - The parent component rerendered (e.g. itself and all its children)
InputX
rerenders because MainForm
rerenders.
Now I suspect at this point you might be wondering why you also see so many "extra" console.log("Render count InputX", renderCount.current);
logs. This is because in all the components you are not tracking accurate renders to the DOM, e.g. the "commit phase", all the renderCount.current += 1;
and console logs are unintentional side-effects directly in the function body of the components, and because you are rendering the app code within a React.StrictMode
component, some functions and lifecycle methods are invoked twice (only in non-production builds) as a way to help detect issues in your code. (I've emphasized the relevant part below)
- Your component function body (only top-level logic, so this doesn’t include code inside event handlers)
- Functions that you pass to
useState
,set
functions,useMemo
, oruseReducer
- Some class component methods like
constructor
,render
,shouldComponentUpdate
(see the whole list)
You are over-counting the actual component renders to the DOM.
The fix for this is trivial: move these unintentional side-effects into a useEffect
hook callback to be intentional side-effects.
I'm experiencing a challenge with React Hook Form where my child component InputX
re-renders every time there's an update in the parent component or other parts of the application, despite no changes in InputX
itself. I’ve pinpointed that the re-renders are triggered by the use of useFormContext
to access the register
function within InputX
.
Here's a brief outline of my setup:
- The
InputX
component utilizesuseFormContext
specifically for theregister
function. - The form is managed by a
FormProvider
in the parent component.
I've noticed that when I remove useFormContext
from InputX
, the unnecessary re-renders stop. This leads me to believe that something about how useFormContext
is interacting with register
or the context setup might be causing these updates.
import { memo, useRef, useMemo, useEffect } from "react";
import {
useFormContext,
useWatch,
useForm,
FormProvider,
} from "react-hook-form";
import {
MergeFormProvider,
useMergeForm,
useMergeFormUtils,
} from "./MergeFormProvider";
function InputX() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const x = useWatch({ name: "x", control });
renderCount.current += 1;
console.log("Render count InputX", renderCount.current);
const someCalculator = useMemo(() => x.repeat(3), [x]);
return (
<fieldset className="grid border p-4">
<legend>Input X Some calculator {someCalculator}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("x")} placeholder="Input X" />
</fieldset>
);
}
function InputY() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const y = useWatch({ name: "y", control });
renderCount.current += 1;
return (
<fieldset className="grid border p-4">
<legend>Input Y {y}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("y")} placeholder="Input Y" />
</fieldset>
);
}
function TodoByFormID() {
const { formID } = useMergeForm();
/**
* Handle component by form id
*/
return <div></div>;
}
const MemoInputX = memo(InputX);
const MemoInputY = memo(InputY);
function MainForm({ form }) {
const { setFieldOptions } = useMergeFormUtils();
const renderCount = useRef(0);
renderCount.current += 1;
const methods = useForm({
defaultValues: form,
});
const [y, z] = useWatch({
control: methods.control,
name: ["y", "z"],
});
const fieldOptions = useMemo<ISelect[]>(() => {
if (y.length) {
return Array.from({ length: y.length }, (_, index) => ({
label: index.toString(),
value: index + ". Item",
}));
}
return [];
}, [y]);
useEffect(() => {
setFieldOptions(fieldOptions);
}, [fieldOptions]);
return (
<FormProvider {...methods}>
<fieldset>
<legend>Main Form Y Value:</legend>
{y}
</fieldset>
<MemoInputX />
<MemoInputY />
<fieldset className="grid border p-4">
<legend>Input Z {z}</legend>
<div>Render count: {renderCount.current}</div>
<input {...methods.register("z")} placeholder="Input Z" />
</fieldset>
<TodoByFormID />
</FormProvider>
);
}
export default function App() {
const formID = 1;
const form = {
count: [],
x: "",
y: "",
z: "",
};
return (
<MergeFormProvider initialFormID={formID}>
<MainForm form={form} />
</MergeFormProvider>
);
}
For a clearer picture, I’ve set up a simplified version of the problem in this CodeSandbox:
Could anyone explain why useFormContext
is causing these re-renders and suggest a way to prevent them without removing useFormContext
? Any advice on optimizing this setup would be greatly appreciated!
I utilized useFormContext
in InputX
to simplify form handling and avoid prop drilling across multiple component layers in my larger project.
I expected that InputX
would only re-render when its specific data or relevant form state changes (like its own input data).
Note:
- The CodeSandbox link provided is a minimized version of my project. In the full project, I have components several layers deep (grand grand children).
I'm experiencing a challenge with React Hook Form where my child component InputX
re-renders every time there's an update in the parent component or other parts of the application, despite no changes in InputX
itself. I’ve pinpointed that the re-renders are triggered by the use of useFormContext
to access the register
function within InputX
.
Here's a brief outline of my setup:
- The
InputX
component utilizesuseFormContext
specifically for theregister
function. - The form is managed by a
FormProvider
in the parent component.
I've noticed that when I remove useFormContext
from InputX
, the unnecessary re-renders stop. This leads me to believe that something about how useFormContext
is interacting with register
or the context setup might be causing these updates.
import { memo, useRef, useMemo, useEffect } from "react";
import {
useFormContext,
useWatch,
useForm,
FormProvider,
} from "react-hook-form";
import {
MergeFormProvider,
useMergeForm,
useMergeFormUtils,
} from "./MergeFormProvider";
function InputX() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const x = useWatch({ name: "x", control });
renderCount.current += 1;
console.log("Render count InputX", renderCount.current);
const someCalculator = useMemo(() => x.repeat(3), [x]);
return (
<fieldset className="grid border p-4">
<legend>Input X Some calculator {someCalculator}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("x")} placeholder="Input X" />
</fieldset>
);
}
function InputY() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const y = useWatch({ name: "y", control });
renderCount.current += 1;
return (
<fieldset className="grid border p-4">
<legend>Input Y {y}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("y")} placeholder="Input Y" />
</fieldset>
);
}
function TodoByFormID() {
const { formID } = useMergeForm();
/**
* Handle component by form id
*/
return <div></div>;
}
const MemoInputX = memo(InputX);
const MemoInputY = memo(InputY);
function MainForm({ form }) {
const { setFieldOptions } = useMergeFormUtils();
const renderCount = useRef(0);
renderCount.current += 1;
const methods = useForm({
defaultValues: form,
});
const [y, z] = useWatch({
control: methods.control,
name: ["y", "z"],
});
const fieldOptions = useMemo<ISelect[]>(() => {
if (y.length) {
return Array.from({ length: y.length }, (_, index) => ({
label: index.toString(),
value: index + ". Item",
}));
}
return [];
}, [y]);
useEffect(() => {
setFieldOptions(fieldOptions);
}, [fieldOptions]);
return (
<FormProvider {...methods}>
<fieldset>
<legend>Main Form Y Value:</legend>
{y}
</fieldset>
<MemoInputX />
<MemoInputY />
<fieldset className="grid border p-4">
<legend>Input Z {z}</legend>
<div>Render count: {renderCount.current}</div>
<input {...methods.register("z")} placeholder="Input Z" />
</fieldset>
<TodoByFormID />
</FormProvider>
);
}
export default function App() {
const formID = 1;
const form = {
count: [],
x: "",
y: "",
z: "",
};
return (
<MergeFormProvider initialFormID={formID}>
<MainForm form={form} />
</MergeFormProvider>
);
}
For a clearer picture, I’ve set up a simplified version of the problem in this CodeSandbox: https://codesandbox.io/p/sandbox/39397x
Could anyone explain why useFormContext
is causing these re-renders and suggest a way to prevent them without removing useFormContext
? Any advice on optimizing this setup would be greatly appreciated!
I utilized useFormContext
in InputX
to simplify form handling and avoid prop drilling across multiple component layers in my larger project.
I expected that InputX
would only re-render when its specific data or relevant form state changes (like its own input data).
Note:
- The CodeSandbox link provided is a minimized version of my project. In the full project, I have components several layers deep (grand grand children).
2 Answers
Reset to default 2Could anyone explain why
useFormContext
is causing these re-renders and suggest a way to prevent them without removinguseFormContext
?
The useFormContext
hook is not causing extra component rerenders. Note that your InputX
and InputY
components have nearly identical implementations*:
function InputX() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const x = useWatch({ name: "x", control });
renderCount.current += 1;
console.log("Render count InputX", renderCount.current);
const someCalculator = useMemo(() => x.repeat(3), [x]); // *
return (
<fieldset className="grid border p-4">
<legend>Input X Some calculator {someCalculator}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("x")} placeholder="Input X" />
</fieldset>
);
}
function InputY() {
const { register, control } = useFormContext();
const renderCount = useRef(0);
const y = useWatch({ name: "y", control });
renderCount.current += 1;
return (
<fieldset className="grid border p-4">
<legend>Input Y {y}</legend>
<div>Render count: {renderCount.current}</div>
<input {...register("y")} placeholder="Input Y" />
</fieldset>
);
}
* The difference being that InputX
has an additional someCalculator
value it is rendering.
and yet it's only when you edit inputs Y and Z that trigger X to render more often, but when you edit input X, only X re-renders.
This is caused by the parent MainForm
component subscribing, i.e. useWatch
, to changes to the y
and z
form states, and not x
.
const [y, z] = useWatch({
control: methods.control,
name: ["y", "z"],
});
- When the
y
andz
form states are updated, this triggersMainForm
to rerender, which re-renders itself and its entire sub-ReactTree, e.g. its children. This meansMainForm
,MemoInputX
,MemoInputY
, the "input Z" and all the rest of the returned JSX all rerender. - When the
x
form state is updated, only the locally subscribedInputX
(MemoInputX
) component is triggered to rerender.
If you updated MainForm
to also subscribe to x
form state changes then you will see nearly identical rendering results and counts across all three X, Y, and Z inputs.
const [x, y, z] = useWatch({
control: methods.control,
name: ["x", "y", "z"],
});
I expected that
InputX
would only re-render when its specific data or relevant form state changes (like its own input data).
React components render for one of two reasons:
- Their
state
orprops
value updated - The parent component rerendered (e.g. itself and all its children)
InputX
rerenders because MainForm
rerenders.
Now I suspect at this point you might be wondering why you also see so many "extra" console.log("Render count InputX", renderCount.current);
logs. This is because in all the components you are not tracking accurate renders to the DOM, e.g. the "commit phase", all the renderCount.current += 1;
and console logs are unintentional side-effects directly in the function body of the components, and because you are rendering the app code within a React.StrictMode
component, some functions and lifecycle methods are invoked twice (only in non-production builds) as a way to help detect issues in your code. (I've emphasized the relevant part below)
- Your component function body (only top-level logic, so this doesn’t include code inside event handlers)
- Functions that you pass to
useState
,set
functions,useMemo
, oruseReducer
- Some class component methods like
constructor
,render
,shouldComponentUpdate
(see the whole list)
You are over-counting the actual component renders to the DOM.
The fix for this is trivial: move these unintentional side-effects into a useEffect
hook callback to be intentional side-effects.
本文标签: reactjsChild Component Rerenders Due to useFormContext in React Hook FormStack Overflow
版权声明:本文标题:reactjs - Child Component Re-renders Due to useFormContext in React Hook Form - Stack Overflow 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/questions/1745659594a2161807.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论