Skip to main content

Filtering data

This guide covers how to receive filter results from FilterQuery, apply them to local arrays, handle validation errors, and show loading feedback during async operations.

Concept

FilterQuery parses the query but does not apply the filter itself. The component converts user input into a structured configuration (filtering rules) and passes it to the onchange handler.

onchange fires when the user presses Enter or clicks the Search button. The parse prop controls how input is interpreted. The component maintains two internal string representations: text (field labels, displayed in the input) and value (field IDs, the bindable prop). Binding value lets you read or set the current query programmatically — the component converts between IDs and labels automatically.

The onchange event

Listen to onchange to receive the filter when the user submits:

<script>
import { FilterQuery } from "@svar-ui/svelte-filter";

const fields = [
{ id: "first_name", label: "First Name", type: "text" },
{ id: "status", label: "Status", type: "text" },
];

function handleFilter({ value, text, error }) {
console.log("Filter config:", value);
console.log("Query text:", text);
}
</script>

<FilterQuery {fields} onchange={handleFilter} />

value is a set of filtering rules the parser produced. text is the normalized display string. Use it to store a human-readable version of the query.

Parse modes

allowFreeText (default)

Field-syntax terms produce structured rules. Bare words become free-text contains rules against all fields:

<FilterQuery
{fields}
{options}
onchange={({ value }) => applyFilter(value)}
/>

A query like Status: Open urgent produces a filter combining status: equal: "Open" with a cross-field contains: "urgent" rule.

strict

parse="strict" rejects free text and unknown fields. Every term must use valid field syntax:

<FilterQuery
parse="strict"
{fields}
{options}
onchange={({ value, error }) => {
if (error) return;
applyFilter(value);
}}
/>

none

parse="none" skips parsing entirely. The raw input string is passed directly to your handler. This is useful when forwarding the query to a search API or an AI endpoint.

<FilterQuery
parse="none"
onchange={({ text }) => sendToSearchAPI(text)}
/>

In this mode, value in the event payload is the raw input string (same as text), not a set of filtering rules.

Filtering local arrays

createArrayFilter turns filtering rules into a filter function you call against your data:

<script>
import { FilterQuery, createArrayFilter } from "@svar-ui/svelte-filter";
import { getData } from "./data";

const { fields, options, data } = getData();
let filteredData = $state(data);

function handleFilter({ value, error }) {
if (error && error.code !== "NO_DATA") return;

const filter = createArrayFilter(value, {}, fields);
filteredData = filter(data);
}
</script>

<FilterQuery {fields} {options} onchange={handleFilter} />

createArrayFilter(config, opts, fields) returns a function (data: any[]) => any[]. Pass fields as the third argument so the filter knows each field's type and applies type-correct comparisons for numbers and dates.

Understanding filter value

Filtering rules produced by FilterQuery are accessible as value in the onchange callback. Each rule is an object with the following structure:

{
field: string; // field ID, or "*" for cross-field rules
type?: "text" | "number" | "date";
predicate?: "year" | "month" | "yearMonth";
filter?: "equal", "contains", "greater", "between"; // etc
includes?: (string | number | Date)[];
value?: string | number | Date | { start: Date; end: Date };
}

You can find more details on filtering rules and its types in the following article: Rule sctructure.

Representative query-to-output mappings:

QueryOutput
status: Open{ field: "status", filter: "equal", value: "Open" }
status: Open, Closed{ field: "status", includes: ["Open", "Closed"] }
age: >25{ field: "age", filter: "greater", value: 25 }
age: 25 .. 50{ field: "age", filter: "between", value: { start: 25, end: 50 } }
name: *Alex*{ field: "name", filter: "contains", value: "Alex" }
start: 2024{ field: "start", predicate: "year", filter: "equal", value: 2024 }
#urgent{ field: "*", filter: "equal", value: "urgent" }

A compound query like project: Alpha and status: Open produces:

{
"glue": "and",
"rules": [
{ "field": "project", "filter": "equal", "value": "Alpha" },
{ "field": "status", "filter": "equal", "value": "Open" }
]
}

rules can contain nested rule objects for grouped conditions like project: Alpha and (status: Open or status: "In Progress").

Handling errors

When the query contains unrecognized fields or invalid syntax, onchange delivers an error object:

interface ValidationError {
code: string; // "NO_DATA" | "INVALID_FIELD" | "INVALID_VALUE" | ...
field: string;
value: string;
message: string;
}

NO_DATA means the query is structurally valid but matches no records. The value property still contains a filtering rule, so applying the filter returns an empty array. All other error codes indicate a syntax problem. In those cases, value may be null.

<script>
import { FilterQuery, createArrayFilter } from "@svar-ui/svelte-filter";

const { fields, options, data } = getData();
let filteredData = $state(data);
let errorMessage = $state("");

function handleFilter({ value, error }) {
if (error) {
errorMessage = error.message;
if (error.code !== "NO_DATA") return;
} else {
errorMessage = "";
}

const filter = createArrayFilter(value, {}, fields);
filteredData = filter(data);
}
</script>

<FilterQuery {fields} {options} onchange={handleFilter} />
{#if errorMessage}
<p class="error">{errorMessage}</p>
{/if}

The component already underlines invalid tokens in the input in real-time. The error object in onchange lets you also show a notification or message in your own UI.

Async loading with progress bar

For async handlers such as backend filtering or AI-powered natural language parsing, call startProgress before the operation and endProgress in the finally block. A thin animated bar appears at the top of the component while the request is running:

<script>
import { FilterQuery, createArrayFilter, getQueryString } from "@svar-ui/svelte-filter";

const { fields, options, data } = getData();
let textValue = $state("");
let filteredData = $state(data);

async function handleFilter({ value, error, text, startProgress, endProgress }) {
// If the user typed free text (no field syntax), send it to an AI endpoint
if (text) {
error = null;
try {
startProgress();
value = await textToFilter(text, fields);
// Write the returned query string back into the input
textValue = value ? getQueryString(value).query : "";
} catch (e) {
error = e;
} finally {
endProgress();
}
}

if (error && error.code !== "NO_DATA") return;

const filter = createArrayFilter(value, {}, fields);
filteredData = filter(data);
}

async function textToFilter(text, fields) {
const res = await fetch("/api/text-to-filter", {
method: "POST",
body: JSON.stringify({ text, fields }),
});
return res.json();
}
</script>

<FilterQuery
value={textValue}
placeholder="e.g., FirstName: contains Alex and Age: >30"
{fields}
{options}
onchange={handleFilter}
/>

The bar animates from left to right over three seconds. Calling startProgress again restarts the animation from the beginning.

getQueryString(value).query serializes filtering rule object back to a query string so you can write the AI-returned filter config back into the value prop.


Related sample: FilterQuery: mixed