Optimizing rerenders
Formula is built with granular updates in mind. If you’re only using built-in controls, then rerendering is already
optimized. In the example below, typing in the input does not cause MyForm to rerender. The number of renders will
stay at 1 (or 2 in strict mode).
function MyForm() { const renderCount = useRef(0); renderCount.current++; const form = useForm({ initialValues: { name: "" }, submit: values => doSubmit(values) }); return ( <form onSubmit={form.submit}> <div>MyForm renders: { renderCount.current }</div> Name: <Input field={form("name")} /> </form> )}As you build more complex forms and make use of Formula’s hooks (except for useForm), you may introduce unnecessary
rerenders. This isn’t always an issue, but it can affect performance. You should write whatever’s most readable and
optimize it once you know there’s an issue.
Suppose we need the name value elsewhere in our form, so we modify our code to use the
useFieldData hook:
const renderCount = useRef(0);renderCount.current++;const form = useForm({/*...*/});const name = useFieldData(form("name"));return ( <form onSubmit={form.submit}> <div>MyForm renders: { renderCount.current }</div> Name: <Input field={form("name")} /> <div>Your name is { name }</div> </form>)After this change, typing in the name input rerenders the entirety of MyForm, when really only the second <div>
needs to rerender.
As is usually the case for React, optimizing rerenders is about putting things as far down in the tree as possible.
We can pass Formula fields as props, which is shown below. In fact, you could pass the whole form instance if you
wanted to. Accessing a field doesn’t create a dependency. Only when you use hooks like useFieldData do you
create a dependency on the field’s value.
function MyForm() { const renderCount = useRef(0); renderCount.current++; const form = useForm({/*...*/}); return ( <form onSubmit={form.submit}> <div>MyForm renders: { renderCount.current }</div> Name: <Input field={form("name")} /> <NameSection nameField={form("name")} /> </form> )}
function NameSection(props: { nameField: FormField<string> }) { const name = useFieldData(props.nameField); return ( <div>Your name is { name }</div> )}The updated code now only rerenders NameSection when you type in the input. MyForm doesn’t need to rerender because
it doesn’t depend on the name. Some people might like this code, but it’s unfortunate to force you to create an extra
component and to structure your code in a specific way.
For this reason, every hook in Formula ships with an equivalent component. You can use these components instead of
defining an otherwise-unnecessary component of your own. The equivalent component for useFieldData is
FieldData. The components aren’t special. They’re simply implemented using their
respective hooks.
function MyForm() { const renderCount = useRef(0); renderCount.current++; const form = useForm({/*...*/}); return ( <form onSubmit={form.submit}> <div>MyForm renders: { renderCount.current }</div> Name: <Input field={form("name")} /> <FieldData field={form("name")}> { (name: string) => <div>Your name is { name }</div> } </FieldData> </form> )}Like the previous version, this code prevents MyForm from rendering, but it’s more concise and some people may find
it more readable.
Hooks and their equivalent components
Section titled “Hooks and their equivalent components”| Hook | Component |
|---|---|
| useElements | ForEachElement |
| useFieldData | FieldData |
| useFieldErrors | FieldErrors |
| useIsSubmitting | IsSubmitting |
| useSubmissionError | SubmissionError |