absent-sapphire
absent-sapphire2mo ago

Select in ant dropdown filter

I'm building a custom table column dropdown filter using ant design components that uses a <Select> component as the filter input, and I'm having trouble with it rendering correctly. Initially, it displays all the options and, when an option is selected, it filters correctly, but subsequently, when I open the <DropdownFilter> again, the select displays the internal value of the selected option, not the label. Here's the relevant code: <FilterDropdown {...props}> <Select placeholder={filterPrompt} options={columnProps.filterProps?.enumerations} allowClear value={props.selectedKeys?.[0] || undefined} onChange={(val) => { props.setSelectedKeys(val ? [val] : []); props.confirm(); }} /> </FilterDropdown> How can I get the select to always render the label of the selected option?
17 Replies
kapa.ai
kapa.ai2mo ago
Hi @Hypnotic Meat. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The issue you're experiencing—where the Ant Design <Select> in a <FilterDropdown> shows the internal value (e.g., an ID) instead of the label after filtering or re-opening—is a known challenge when the value types (string vs. number) or mapping between value and label are not handled correctly. This is especially common when the filter value comes from the URL or is synced with location, as Ant Design always normalizes selected keys to strings, but your options may use numbers or other types as values. Solution: Use the mapValue prop in <FilterDropdown> to normalize value types Refine provides a mapValue prop on <FilterDropdown> to help you map the selected keys (which are always strings from Ant Design) back to the correct type (e.g., number) expected by your options. This ensures that the <Select> component can correctly match the value to its label and display the label instead of the raw value. Here's how you can update your code:
<FilterDropdown
{...props}
mapValue={(selectedKeys) => selectedKeys.map((i) => parseInt(i.toString()))}
>
<Select
placeholder={filterPrompt}
options={columnProps.filterProps?.enumerations}
allowClear
value={props.selectedKeys?.[0] || undefined}
onChange={(val) => {
props.setSelectedKeys(val ? [val] : []);
props.confirm();
}}
/>
</FilterDropdown>
<FilterDropdown
{...props}
mapValue={(selectedKeys) => selectedKeys.map((i) => parseInt(i.toString()))}
>
<Select
placeholder={filterPrompt}
options={columnProps.filterProps?.enumerations}
allowClear
value={props.selectedKeys?.[0] || undefined}
onChange={(val) => {
props.setSelectedKeys(val ? [val] : []);
props.confirm();
}}
/>
</FilterDropdown>
- The mapValue function converts the string keys back to numbers (or whatever type your option values are). - This ensures that the <Select> component can find the correct label for the selected value and display it properly. Reference Example from Refine Docs:
<FilterDropdown
{...props}
mapValue={(selectedKeys) => selectedKeys.map((i) => parseInt(i.toString()))}
>
<Select
style={{ minWidth: 200 }}
mode="multiple"
placeholder="Select Category"
{...categorySelectProps}
/>
</FilterDropdown>
<FilterDropdown
{...props}
mapValue={(selectedKeys) => selectedKeys.map((i) => parseInt(i.toString()))}
>
<Select
style={{ minWidth: 200 }}
mode="multiple"
placeholder="Select Category"
{...categorySelectProps}
/>
</FilterDropdown>
See the official documentation for more details and examples. Why this happens:
Ant Design's Table filter system always serializes filter values as strings (especially when syncing with the URL), but your <Select> options may use numbers or other types. Without mapping, the <Select> can't match the value to the label, so it just displays the raw value. Summary:
Add the mapValue prop to your <FilterDropdown> and ensure it converts the selected keys to the correct type for your <Select> options. This will ensure the label is always displayed correctly, even after filtering or page reloads. FilterDropdown mapValue property documentation ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@224018850419572736> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
absent-sapphire
absent-sapphireOP2mo ago
@kapa.ai changing it to onSelect fixed my issue, thanks. Now I have a different problem with a <DatePicker> filter. Consider the following code: <FilterDropdown {...props}> <DatePicker placeholder={filterPrompt} /> </FilterDropdown> When I initially filter, I can see that the url parameter is correctly passing the selected date. However, when I open the filter dropdown subsequently, I get the error: "date.isValid is not a function". What am I doing wrong?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- The error "date.isValid is not a function" when re-opening a <DatePicker> filter in a <FilterDropdown> is a known issue that occurs because, when the filter value is restored from the URL (with syncWithLocation enabled), it is a string rather than a Dayjs object. Ant Design's <DatePicker> expects a Dayjs object as its value, so passing a string causes this error. How to fix:
You need to use the mapValue prop on <FilterDropdown> to convert the string value (from the URL or filter state) back into a Dayjs object before passing it to <DatePicker>. Here’s how you can do it:
import dayjs from "dayjs";

<FilterDropdown
{...props}
mapValue={(selectedKeys, event) => {
if (event === "value") {
// Convert string to Dayjs object for DatePicker
return selectedKeys.map((key) => (typeof key === "string" ? dayjs(key) : key));
}
// For onChange, just return the ISO string
return selectedKeys;
}}
>
<DatePicker placeholder={filterPrompt} />
</FilterDropdown>
import dayjs from "dayjs";

<FilterDropdown
{...props}
mapValue={(selectedKeys, event) => {
if (event === "value") {
// Convert string to Dayjs object for DatePicker
return selectedKeys.map((key) => (typeof key === "string" ? dayjs(key) : key));
}
// For onChange, just return the ISO string
return selectedKeys;
}}
>
<DatePicker placeholder={filterPrompt} />
</FilterDropdown>
This approach ensures that: - When the filter value is restored from the URL (as a string), it is converted to a Dayjs object for the <DatePicker>. - When the value changes, it is stored as a string (ISO format), which is compatible with URL sync and Refine’s filtering. Reference:
This pattern is described in the Refine documentation for handling date filters with <FilterDropdown>, especially when using syncWithLocation or restoring filter values from the URL. The same principle is used for <DatePicker.RangePicker> with the rangePickerFilterMapper utility, but for a single date, you can use a similar mapping function as shown above.
See the official documentation for details and examples. Summary:
Always use mapValue to convert string values from the filter state or URL into Dayjs objects before passing them to <DatePicker>. This will prevent the "date.isValid is not a function" error and ensure your filter works correctly after page reloads or when restoring from the URL. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@224018850419572736> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
absent-sapphire
absent-sapphireOP2mo ago
@kapa.ai when I add the mapValue function, it errors out that selectedKeys.map is not a function. When I log it to console, I can see that selectedKeys is a dayjs object
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're encountering this error because, depending on the event and the state of the filter, selectedKeys can be either an array or a single value (such as a Dayjs object or a string). The .map function only works on arrays, so if selectedKeys is not an array, calling .map will throw an error. How to handle this: You should check the type of selectedKeys before calling .map. If it's not an array, wrap it in an array or handle it accordingly. Here’s an example pattern:
import dayjs from "dayjs";

<FilterDropdown
{...props}
mapValue={(selectedKeys, event) => {
// Ensure selectedKeys is always an array
const keys = Array.isArray(selectedKeys) ? selectedKeys : [selectedKeys];

if (event === "value") {
return keys.map((key) =>
typeof key === "string" ? dayjs(key) : key
);
}
return keys;
}}
>
<DatePicker placeholder={filterPrompt} />
</FilterDropdown>
import dayjs from "dayjs";

<FilterDropdown
{...props}
mapValue={(selectedKeys, event) => {
// Ensure selectedKeys is always an array
const keys = Array.isArray(selectedKeys) ? selectedKeys : [selectedKeys];

if (event === "value") {
return keys.map((key) =>
typeof key === "string" ? dayjs(key) : key
);
}
return keys;
}}
>
<DatePicker placeholder={filterPrompt} />
</FilterDropdown>
This approach ensures that .map is always called on an array, preventing the error you saw. Reference:
This pattern is consistent with the guidance in the Refine documentation, which notes that filter values restored from the URL may not always be in the expected format and may require normalization before being passed to Ant Design components. The documentation also highlights the importance of type normalization when using the mapValue prop, especially for date and select filters, to avoid type mismatches and runtime errors FilterDropdown mapValue property documentation. If you continue to see issues, double-check the value of selectedKeys in both "value" and "onChange" events, as their structure may differ depending on how the filter is being restored or updated. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@224018850419572736> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
absent-sapphire
absent-sapphireOP2mo ago
@kapa.ai ok, now when I filter, a filter value for each property on the dayjs object is being added to the url. I only want the serialized date string to be passed to the server backend. How can I modify it to do that?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- The issue you're seeing—where each property of the Dayjs object is being added to the URL—happens when the filter value is not properly serialized to a string before being stored or synced with the URL. This typically occurs if the mapValue function does not convert the Dayjs object to an ISO string on the "onChange" event. How to fix:
You need to ensure that, in your mapValue function, you convert the Dayjs object to a string (e.g., using .toISOString()) when the event is "onChange". This way, only the serialized date string will be stored in the filter state and passed to the backend. Here’s how you can modify your mapValue function:
import dayjs from "dayjs";

<FilterDropdown
{...props}
mapValue={(selectedKeys, event) => {
const keys = Array.isArray(selectedKeys) ? selectedKeys : [selectedKeys];

if (event === "value") {
// For the DatePicker input, convert string to Dayjs
return keys.map((key) =>
typeof key === "string" ? dayjs(key) : key
);
}
if (event === "onChange") {
// For filter state/URL, convert Dayjs to ISO string
return keys.map((key) =>
dayjs.isDayjs(key) ? key.toISOString() : key
);
}
return keys;
}}
>
<DatePicker placeholder={filterPrompt} />
</FilterDropdown>
import dayjs from "dayjs";

<FilterDropdown
{...props}
mapValue={(selectedKeys, event) => {
const keys = Array.isArray(selectedKeys) ? selectedKeys : [selectedKeys];

if (event === "value") {
// For the DatePicker input, convert string to Dayjs
return keys.map((key) =>
typeof key === "string" ? dayjs(key) : key
);
}
if (event === "onChange") {
// For filter state/URL, convert Dayjs to ISO string
return keys.map((key) =>
dayjs.isDayjs(key) ? key.toISOString() : key
);
}
return keys;
}}
>
<DatePicker placeholder={filterPrompt} />
</FilterDropdown>
Why this works:
- "value" event: Ensures the DatePicker always receives a Dayjs object. - "onChange" event: Ensures only the ISO string is stored in the filter state and URL, preventing all Dayjs properties from being serialized. Reference:
This approach is directly aligned with the official Refine documentation, which explains how to use mapValue to convert between Dayjs objects and ISO strings for date filters, especially when using URL sync or restoring filter values from the URL. See the FilterDropdown documentation for more details and examples. --- Summary:
Update your mapValue function to serialize Dayjs objects to ISO strings on "onChange". This will ensure only the date string is passed to your backend and stored in the URL, resolving the issue. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@224018850419572736> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
absent-sapphire
absent-sapphireOP2mo ago
@kapa.ai ok, that's working. Now, how can I add an additional control to the <FilterDropdown> that allows users to change the type of filtering for the date picker to things like 'before' or 'after'?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- The knowledge sources indicate that, by default, Refine’s <FilterDropdown> and Ant Design’s table filtering use the eq operator for simple values and in for arrays. To use a different operator (like gte for "after" or lte for "before") with a date picker, you need to set the initial filter with the desired operator using the initial filter option in useTable. This approach is confirmed in Github Issue #1199, where setting an initialFilter with an undefined value allows you to specify the operator. However, dynamically changing the operator from within the filter dropdown UI (e.g., letting the user pick "before", "after", or "on" from a select) is not natively supported out-of-the-box in Refine or Ant Design, according to the current documentation and issues. There are feature requests for this (#1691, #1373), but as of now, you would need to implement a custom solution. What you can do: - You can add a <Select> inside your <FilterDropdown> to let the user choose the operator. - In your onChange handler, you would need to update both the value and the operator in the filter state. - However, Refine’s current filter handling does not provide a direct way to change the operator from the dropdown UI; it expects the operator to be set in the filter definition or via initialFilter. Summary:
There is no built-in way in Refine’s <FilterDropdown> to let users dynamically change the filter operator (e.g., "before", "after", "on") for a date picker from the dropdown UI. You can set the operator via initialFilter in useTable, but for a fully dynamic UI, you would need to implement custom filter logic and state management outside of the standard Refine hooks. If you need a reference for setting the operator via initialFilter, see this comment:
I can pass initialFilter with undefined value and useTable will use operator from initialFilter.
If you want to explore a custom solution, you may need to manage the filter state yourself and update both the value and operator based on user input, but this is not covered in the official documentation or examples. --- If you need a code example or further guidance on a custom implementation, let me know! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@224018850419572736> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
absent-sapphire
absent-sapphireOP2mo ago
@kapa.ai yeah, can you provide a custom code sample for a simple way of setting both the filter value and filter operator?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Certainly! Based on the knowledge sources, there is no built-in way in Refine’s <FilterDropdown> to dynamically change the filter operator from the dropdown UI. However, you can implement a custom filter UI outside of the standard column filter, using a form and the onSearch prop of useTable to set both the value and the operator. This approach is demonstrated in the official documentation and blog examples for advanced table search and filtering. Here’s a simplified example using a form with a <Select> for the operator and a <DatePicker> for the value, then passing both to onSearch in useTable:
import React from "react";
import { useTable, List } from "@refinedev/antd";
import { Form, DatePicker, Select, Button, Row, Col, Table } from "antd";
import dayjs from "dayjs";

const operatorOptions = [
{ label: "On", value: "eq" },
{ label: "Before", value: "lte" },
{ label: "After", value: "gte" },
];

export const CustomDateFilterList = () => {
const { searchFormProps, tableProps } = useTable({
onSearch: (params) => {
const filters = [];
const { date, operator } = params;
if (date && operator) {
filters.push({
field: "createdAt",
operator,
value: date.toISOString(),
});
}
return filters;
},
});
return (
<Row gutter={[16, 16]}>
<Col lg={6} xs={24}>
<Form layout="vertical" {...searchFormProps}>
<Form.Item label="Date Operator" name="operator" initialValue="eq">
<Select options={operatorOptions} />
</Form.Item>
<Form.Item label="Date" name="date">
<DatePicker />
</Form.Item>
<Form.Item>
<Button htmlType="submit" type="primary">
Filter
</Button>
</Form.Item>
</Form>
</Col>
<Col lg={18} xs={24}>
<List>
<Table {...tableProps} rowKey="id">
{/* ...your columns here... */}
</Table>
</List>
</Col>
</Row>
);
};
import React from "react";
import { useTable, List } from "@refinedev/antd";
import { Form, DatePicker, Select, Button, Row, Col, Table } from "antd";
import dayjs from "dayjs";

const operatorOptions = [
{ label: "On", value: "eq" },
{ label: "Before", value: "lte" },
{ label: "After", value: "gte" },
];

export const CustomDateFilterList = () => {
const { searchFormProps, tableProps } = useTable({
onSearch: (params) => {
const filters = [];
const { date, operator } = params;
if (date && operator) {
filters.push({
field: "createdAt",
operator,
value: date.toISOString(),
});
}
return filters;
},
});
return (
<Row gutter={[16, 16]}>
<Col lg={6} xs={24}>
<Form layout="vertical" {...searchFormProps}>
<Form.Item label="Date Operator" name="operator" initialValue="eq">
<Select options={operatorOptions} />
</Form.Item>
<Form.Item label="Date" name="date">
<DatePicker />
</Form.Item>
<Form.Item>
<Button htmlType="submit" type="primary">
Filter
</Button>
</Form.Item>
</Form>
</Col>
<Col lg={18} xs={24}>
<List>
<Table {...tableProps} rowKey="id">
{/* ...your columns here... */}
</Table>
</List>
</Col>
</Row>
);
};
This pattern is consistent with the advanced search and filter examples in the Refine documentation, where you use a custom form to control both the filter value and operator, and return a CrudFilters array in onSearch to update the table data accordingly
Advanced Table Search Example
Blog Example: Customizable Issue Tracker. Note:
If you want to use this approach in a column filter dropdown, you would need to manage the filter state yourself and update both the value and operator, but this is not directly supported by Refine’s built-in <FilterDropdown> as of now. If you need a column filter dropdown version, let me know! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@224018850419572736> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?