Custom components with fallbacks
Sometimes a default component nearly meets your needs, but requires minor behavioral modifications. Rather than recreating the entire component, you can implement custom behavior and fall back to the default component when appropriate. This approach keeps your implementation current with standard features while maintaining customization flexibility.
Consider a scenario where you need a custom date picker for specific fields, while using the standard value editor for others. The default ValueEditor
lacks date picker functionality, requiring a custom solution.
Instead of duplicating the default ValueEditor
code, you can leverage its existing functionality by spreading the same props (<ValueEditor {...props} />
) and returning it when your custom behavior doesn't apply.
This example creates a custom value editor using the react-datepicker
library. We'll start by configuring the fields
array with standard Field
objects, adding a custom datatype
attribute to date fields that signals when to display the date picker.
// fields.ts
import { Field } from 'react-querybuilder';
export const fields: Field[] = [
{
name: 'name',
label: 'Name',
operators: [
{ name: '=', label: 'is' },
{ name: 'beginsWith', label: 'begins with' },
],
},
{
name: 'dateOfBirth',
label: 'Date of Birth',
operators: [{ name: '=', label: 'is' }],
datatype: 'date',
},
{
name: 'dateRange',
label: 'Date Range',
operators: [{ name: 'between', label: 'is between' }],
datatype: 'dateRange',
},
];
The custom value editor displays different interfaces based on the field's datatype
:
"date"
: Standard date picker"dateRange"
: Date range picker- Other values or
undefined
: Falls back to the defaultValueEditor
We use the date-fns
library for date parsing and formatting. Storing dates as strings (rather than Date
objects) keeps the query object serializable for JSON.stringify
. Date ranges are stored as comma-separated string pairs.
// CustomValueEditor.tsx
import { format, parse } from 'date-fns';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import { ValueEditor, ValueEditorProps } from 'react-querybuilder';
const dateFormat = 'yyyy-MM-dd';
export const CustomValueEditor = (props: ValueEditorProps) => {
if (props.fieldData.datatype === 'date') {
return (
<div>
<DatePicker
dateFormat={dateFormat}
selected={!props.value ? null : parse(props.value, dateFormat, new Date())}
onChange={(d: Date) => props.handleOnChange(d ? format(d, dateFormat) : null)}
/>
</div>
);
} else if (props.fieldData.datatype === 'dateRange') {
const [startDate, endDate] = props.value.split(',');
return (
<div>
<DatePicker
selectsRange
dateFormat={dateFormat}
startDate={!startDate ? null : parse(startDate, dateFormat, new Date())}
endDate={!endDate ? null : parse(endDate, dateFormat, new Date())}
onChange={(update: [Date, Date]) => {
const [s, e] = update;
props.handleOnChange(
[!s ? '' : format(s, dateFormat), !e ? '' : format(e, dateFormat)].join(',')
);
}}
/>
</div>
);
}
return <ValueEditor {...props} />;
};
If you're using one of the compatibility packages, you probably want to fall back to the value editor from that package instead of ValueEditor
from the main package. For example, when using @react-querybuilder/antd
, fall back to AntDValueEditor
:
-import { ValueEditor, ValueEditorProps } from 'react-querybuilder';
+import { AntDValueEditor } from '@react-querybuilder/antd';
+import { ValueEditorProps } from 'react-querybuilder';
- return <ValueEditor {...props} />;
+ return <AntDValueEditor {...props} />;
Configure the QueryBuilder
component to use the custom value editor through the controlElements
prop:
// App.tsx
import { useState } from 'react';
import { CustomValueEditor } from './CustomValueEditor';
import { fields } from './fields';
export default function App() {
const [query, setQuery] = useState({ combinator: 'and', rules: [] });
return (
<QueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
controlElements={{ valueEditor: CustomValueEditor }}
/>
);
}
The interactive demo below shows how each field type behaves: the "Name" field displays a text input, "Date of Birth" shows a standard date picker, and "Date Range" presents a date range picker.
Other examples of the "fallback" technique can be seen in the Limit rule groups page and these two StackOverflow answers.