Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Keep filters when refresh the page with useTable #6001

Closed
dfang opened this issue May 29, 2024 · 6 comments · Fixed by #6019
Closed

[FEAT] Keep filters when refresh the page with useTable #6001

dfang opened this issue May 29, 2024 · 6 comments · Fixed by #6019
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@dfang
Copy link

dfang commented May 29, 2024

Is your feature request related to a problem? Please describe.

this is a list of customers filtered with idle status,

https://example.admin.refine.dev/customers?pageSize=10&current=9&sorters[0][field]=isActive&sorters[0][order]=asc&filters[0][field]=fullName&filters[0][operator]=contains

when I refresh the page, the list is not filtered with idle status.

Describe alternatives you've considered

No response

Additional context

when you filter then click pagination number, it's ok. but refresh and click pagination number, it's not filtered

Describe the thing to improve

keep the filter after refreshing the page (restore filters from url)

@dfang dfang added the enhancement New feature or request label May 29, 2024
@alicanerdurmaz alicanerdurmaz added the good first issue Good for newcomers label May 29, 2024
@alicanerdurmaz
Copy link
Member

alicanerdurmaz commented May 29, 2024

Hello @dfang, thanks for the issue!

we need to add defaultFilteredValue={getDefaultFilter("isActive", filters, "eq")} to <Table.Column /> to populate column's default value.

more information is here: https://refine.dev/docs/ui-integrations/ant-design/hooks/use-table/#initial-filter-and-sorter

Do you want to work on this?

@dzcpy
Copy link
Contributor

dzcpy commented May 31, 2024

Also experinced this problem. Hopefully it can be added soon!
By the way here's how I fixed it by a workaround:

  const initialFilters: {[key: string]: string}[] = []

  const currentUrl = new URL(location.href)
  currentUrl.searchParams.forEach((value, key) => {
    const matches = key.match(/filters\[(\d+)\]\[(.*?)\]/);
    if (matches) {
      const index = Number(matches[1]);
      const field = matches[2];
  
      // Ensure the filter object exists
      if (!initialFilters[index]) {
        initialFilters[index] = {};
      }
  
      // Assign the value to the appropriate field
      initialFilters[index][field] = value;
    }
  })
        <Table.Column
          dataIndex="inspector"
          key="inspector][id"
          title="Inspector"
          className="cursor-pointer"
          render={(value) => value?.name}
          defaultFilteredValue={initialFilters.filter(filter => filter.field === 'inspector][id').map(filter => filter.value)}
          filterDropdown={(props) => (
            <FilterDropdown {...props}>
              <Select style={{ minWidth: 200 }} placeholder="Select inspector" {...inspectorsSelectProps} />
            </FilterDropdown>
          )}
        />

@alicanerdurmaz
Copy link
Member

Hello @dzcpy,

Is this not working?

import { getDefaultFilter } from "@refinedev/core";

const { filters } = useTable();

// ...
defaultFilteredValue={getDefaultFilter("inspector][id", filters, "eq")}
// ...

@dzcpy
Copy link
Contributor

dzcpy commented May 31, 2024

Hello @dzcpy,

Is this not working?

import { getDefaultFilter } from "@refinedev/core";

const { filters } = useTable();

// ...
defaultFilteredValue={getDefaultFilter("inspector][id", filters, "eq")}
// ...

It doesn't work properly. It's just a hack. But this feature should really be integrated into Refine just like other fields (sorter and pagination from URL, if syncWithLocation is set to true)

Also when the field is a number or a boolean value, I need to convert the value to the respective type, otherwise filter select / checkbox will not find the default selected value.

I've changed it to something like this:

import { isNil } from 'lodash'

export const tableFiltersFromUrl = (url?: string) => {
  if (!url) {
    url = window.location.href
  }
  return Array.from(new URL(url).searchParams.entries()).reduce((acc, [key, value]) => {
    if (key.startsWith('filters[')) {
      const matches = key.match(/filters\[(?<index>\d+)\]\[(?<item>.*?)\]/)
      if (matches?.groups?.index && matches?.groups?.item) {
        const index = Number(matches.groups.index)
        const field = matches.groups.item

        // Ensure the filter object exists
        if (!acc[index]) {
          acc[index] = {}
        }

        // Assign the value to the appropriate field
        acc[index][field] = value
      }
    }
    return acc
  }, [] as { [key: string]: string }[])
    .filter(filter => filter.field && filter.operator && !isNil(filter.value)) as { field: string, operator: string, value: string }[]
}

export const tableFiltersGetDefaultValue = (filters: ReturnType<typeof tableFiltersFromUrl>, field: string, type: 'string' | 'number' | 'boolean' = 'string') =>
  filters.filter(filter => filter.field === field).map(filter => type === 'boolean' ? /^true$/i.test(filter.value) : type === 'number' ? Number(filter.value) : filter.value)
const initialFilters = useMemo(() => tableFiltersFromUrl(), [])
<Table.Column
  dataIndex="name"
  key="name"
  title="Name"
  defaultFilteredValue={tableFiltersGetDefaultValue(initialFilters, 'name')}
  filterDropdown={(props) => (
    <FilterDropdown {...props}>
      <Input style={{ minWidth: 200 }} placeholder="Please input name" />
    </FilterDropdown>
  )}
  className="cursor-pointer"
/>
<Table.Column
  dataIndex="inspector"
  key="inspector][id"
  title="Inspector"
  className="cursor-pointer"
  render={(value) => value?.name}
  defaultFilteredValue={tableFiltersGetDefaultValue(initialFilters, 'inspector][id', 'number')}
  filterDropdown={(props) => (
    <FilterDropdown {...props}>
      <Select style={{ minWidth: 200 }} placeholder="Please select inspector" {...inspectorsSelectProps} />
    </FilterDropdown>
  )}
/>
<Table.Column
  dataIndex="finished"
  key="finished"
  title="Finished?"
  render={(value) => (value ? locationFinishedOptions.true : locationFinishedOptions.false)}
  defaultFilteredValue={tableFiltersGetDefaultValue(initialFilters, 'finished', 'boolean')}
  filterDropdown={(props) => (
    <FilterDropdown {...props}>
      <Select
        style={{ minWidth: 200 }}
        placeholder="Choose if it's finished"
        filterOption={false}
        options={Object.entries(locationFinishedOptions).map(([value, label]) => ({ value, label }))}
      />
    </FilterDropdown>
  )}
  className="cursor-pointer"
/>

@alicanerdurmaz
Copy link
Member

@dzcpy Thanks for the detailed explanation, you are right there is room for improvement. We'll think about it.

@ahmedmelfay
Copy link

This doesnt work if you have different filters component, e.g here, would appreciate if someone has a solution for this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants