React Hook Form Query Builder Integration
Treat the query as a form field with validation, change tracking, submission, and reset.
What this recipe builds
- Query data participates in form validation and change tracking.
- Submit and reset use the same default values.
- Builder validation remains visible beside form errors.
Loading the interactive recipe demo...
Install and imports
Install / imports
npm install @vojtechportes/react-query-builder react-hook-form
Fields and initial query
Typed field configuration
type FilterForm = { name: string; query: DenormalizedQuery };const fields: IBuilderFieldProps[] = [{ field: 'email', label: 'Email', type: 'TEXT' },{ field: 'createdAt', label: 'Created', type: 'DATE' },];const initialQuery: DenormalizedQuery = [{type: 'GROUP',value: 'AND',isNegated: false,children: [],},];
Builder implementation
React implementation
import '@vojtechportes/react-query-builder/styles.css';import React from 'react';import { Controller, useForm } from 'react-hook-form';import {Builder,type DenormalizedQuery,type IBuilderFieldProps,} from '@vojtechportes/react-query-builder';interface IFilterForm {name: string;query: DenormalizedQuery;}const fields: IBuilderFieldProps[] = [{field: 'email',label: 'Email',type: 'TEXT',operators: ['EQUAL', 'CONTAINS'],},{field: 'createdAt',label: 'Created',type: 'DATE',operators: ['EQUAL', 'LARGER_EQUAL', 'SMALLER_EQUAL'],},];const initialQuery: DenormalizedQuery = [{type: 'GROUP',value: 'AND',isNegated: false,children: [{ field: 'email', operator: 'CONTAINS', value: '@example.com' }],},];export const HookFormFilter = () => {const { control, handleSubmit, reset, formState } = useForm<IFilterForm>({defaultValues: { name: 'Customer search', query: initialQuery },});return (<form onSubmit={handleSubmit(console.log)}><Controllername="query"control={control}rules={{ validate: (value) => value.length > 0 || 'Add a query root.' }}render={({ field }) => (<Builderfields={fields}data={field.value}onChange={field.onChange}showValidation/>)}/><button type="submit" disabled={!formState.isDirty}>Save</button><button type="button" onClick={() => reset()}>Reset</button></form>);};
Submit the filter form
Submit the filter form
const saveFilter = ({ name, query }: FilterForm) =>fetch('/api/filter-presets', {method: 'POST',body: JSON.stringify({ name, query }),});
Validation and safety
- Validate submitted query data again when it reaches your API.
- Client-side validation only checks the form values. Your server must still decide what the user is allowed to access.
Production notes
- Keep defaultValues stable and call reset after a successful save.
- Do not copy the same form value into a separate useState. Let Controller manage it.
Related guides
Frequently asked questions
Why use Controller instead of register?
Builder does not behave like a native text input. Controller passes its value and changes to React Hook Form for you.
Will React Hook Form reset update Builder too?
Yes, as long as Builder receives its value from Controller. React Hook Form restores defaultValues and passes the restored filter back to Builder.