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

Introduce <FilterLiveForm> #10344

Merged
merged 33 commits into from
Nov 20, 2024
Merged

Introduce <FilterLiveForm> #10344

merged 33 commits into from
Nov 20, 2024

Conversation

slax57
Copy link
Contributor

@slax57 slax57 commented Nov 12, 2024

Problem

The FilterList is designed for buttons - not inputs. We provide the FilterLiveSearch component, which is a TextInput inside a form that submits on change. But some users want to be able to add custom form inputs (e.g. to select an author from a resource using an autocomplete), and re-writing the form wrapper is cumbersome.

Solution

Introduce a new component to wrap form inputs in that case:

export const EventFilterAside = () => (
    <Card>
        <CardContent>
            <FilterList label="Has ordered" icon={<MonetizationOnIcon />}>
                <FilterListItem
                    label="True"
                    value={{
                        nb_commands_gte: 1,
                        nb_commands_lte: undefined,
                    }}
                />
                <FilterListItem
                    label="False"
                    value={{
                        nb_commands_gte: undefined,
                        nb_commands_lte: 0,
                    }}
                />
            </FilterList>
            <FilterLiveForm>
                <TextInput source="q" />
            </FilterLiveForm>
            <FilterLiveForm>
                <ReferenceInput source="authorId" reference="authors" />
            </FilterLiveForm>
        </CardContent>
    </Card>
);

This PR also fixes #5591 🎉

Todo

  • Introduce <AutoSubmitFilterForm>
  • Introduce <FilterListWrapper> to make it look nicer next to <FilterList>
  • Add stories
  • Add tests
  • Write doc
  • Try to fix debounce issue
    • Story
    • Fix
    • Unit test
    • Check if the behavior when there is validation at play is OK
  • Try to fix issue where FilterLiveSearch keeps applying old value
  • Use AutoSubmitFilterForm in FilterForm to keep the code more consistent
  • Use AutoSubmitFilterForm in FilterLiveSearch to keep the code more consistent

How To Test

Additional Checks

  • The PR targets master for a bugfix, or next for a feature
  • The PR includes unit tests (if not possible, describe why)
  • The PR includes one or several stories (if not possible, describe why)
  • The documentation is up to date

Also, please make sure to read the contributing guidelines.

@slax57 slax57 added the WIP Work In Progress label Nov 12, 2024
@@ -30,35 +24,45 @@ import { FilterFormInput } from './FilterFormInput';
import { FilterContext } from '../FilterContext';

export const FilterForm = (props: FilterFormProps) => {
const { defaultValues, filters: filtersProps, ...rest } = props;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultValues is not used in all RA's code, and I tested it and it does not work, so I removed it to reduce tech debt. Shouldn't be a BC since it does not currently work.

Comment on lines -38 to -42
const mergedInitialValuesWithDefaultValues =
mergeInitialValuesWithDefaultValues(
defaultValues || filterValues,
filters
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole call is actually useless, because:

  1. defaultValues don't work
  2. mergeInitialValuesWithDefaultValues actually does nothing, since there is a runtime check to prevent having both alwaysOn and defaultValue. It's probably a historic tech debt, that's why I removed it.

@@ -270,7 +252,7 @@ const getInputValue = (
filterValues: Record<string, any>
) => {
if (formValues[key] === undefined || formValues[key] === null) {
return '';
return get(filterValues, key, '');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, historic bug (see updated unit test)

resolver,
validate,
children,
component: Component,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you provide a default value here and avoid the ternary at L124?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know how to do it, but now I know! Thanks!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 13 out of 28 changed files in this pull request and generated no suggestions.

Files not reviewed (15)
  • docs/navigation.html: Language not supported
  • docs/Reference.md: Evaluated as low risk
  • packages/ra-core/src/util/index.ts: Evaluated as low risk
  • packages/ra-ui-materialui/src/list/filter/FilterList.stories.tsx: Evaluated as low risk
  • packages/ra-ui-materialui/src/list/filter/FilterForm.spec.tsx: Evaluated as low risk
  • packages/ra-ui-materialui/src/list/filter/FilterList.tsx: Evaluated as low risk
  • packages/ra-core/src/controller/list/index.ts: Evaluated as low risk
  • packages/ra-ui-materialui/src/list/filter/FilterForm.tsx: Evaluated as low risk
  • packages/ra-core/src/controller/list/filter/index.ts: Evaluated as low risk
  • packages/ra-core/src/controller/list/filter/AutoSubmitFilterForm.tsx: Evaluated as low risk
  • packages/ra-ui-materialui/src/input/SearchInput.spec.tsx: Evaluated as low risk
  • docs/FilteringTutorial.md: Evaluated as low risk
  • docs/FilterList.md: Evaluated as low risk
  • docs/FilterLiveSearch.md: Evaluated as low risk
  • packages/ra-ui-materialui/src/input/SearchInput.stories.tsx: Evaluated as low risk
djhi

This comment was marked as resolved.

import { Box, BoxProps, Typography } from '@mui/material';
import { useTranslate } from 'ra-core';

export const FilterListWrapper = (props: FilterListWrapperProps) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name this FilterListSection

@@ -12,7 +12,7 @@ title: "The FilterLiveSearch Component"
</video>


The filter sidebar is not a form. Therefore, if your users need to enter complex filters, you'll have to recreate a filter form using react-hook-form (see the [Building a custom filter](./FilteringTutorial.md#building-a-custom-filter) for an example). However, if you only need one text input with a filter-as-you-type behavior, you'll find the `<FilterLiveSearch>` component convenient.
The filter sidebar is not a form. Therefore, if your users need to enter complex filters, you'll have to recreate a filter form. This can easily be done thanks to the [`<AutoSubmitFilterForm>`](./AutoSubmitFilterForm.md) component. However, if you only need one text input with a filter-as-you-type behavior, you'll find the `<FilterLiveSearch>` component even more convenient.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The filter sidebar is not a form. Therefore, if your users need to enter complex filters, you'll have to recreate a filter form. This can easily be done thanks to the [`<AutoSubmitFilterForm>`](./AutoSubmitFilterForm.md) component. However, if you only need one text input with a filter-as-you-type behavior, you'll find the `<FilterLiveSearch>` component even more convenient.
The filter sidebar is not a form. Therefore, if your users need to enter complex filters, you'll have to recreate a filter form. This can be done thanks to the [`<AutoSubmitFilterForm>`](./AutoSubmitFilterForm.md) component. However, if you only need one text input with a filter-as-you-type behavior, you'll find the `<FilterLiveSearch>` component even more convenient.

Comment on lines 7 to 19
export const useDebouncedEvent = (callback, delay: number) => {
// Create a ref that stores the debounced callback
const debouncedCallbackRef = useRef(debounce(callback, delay));

// Whenever callback or delay changes, we need to update the debounced callback
useEffect(() => {
debouncedCallbackRef.current = debounce(callback, delay);
}, [callback, delay]);

// The function returned by useCallback will invoke the debounced callback
// Its dependencies array is empty, so it never changes across re-renders
return useCallback((...args) => debouncedCallbackRef.current(...args), []);
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't have the checks we do in useEvent to prevent calling the callback inside a render. I'm not really sure of the consequences but the react team has made it a strong limitation of useEvent (see its code in our repo)

const {
debounce = 500,
resolver,
validate,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you have a non validated input you can't submit. But if you submit your FilterList in a same time, it will reset your AutoSubmitForm to the last validated state.
Do we need to consider it as an issue or as a normal way of work @fzaninotto @djhi ?

Capture.video.2024-11-18.11.19.16.mp4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this isn't a bug

@erwanMarmelab
Copy link
Contributor

Such a really nice job 🦾

@erwanMarmelab
Copy link
Contributor

Problem

With a FilterLiveSearch or an AutoSubmitFilterForm. If I write something and click on the clear filters button faster than my debounce, the new search query happens after the clearing.

It can easily happens when your app need to increase a bit the debounce.

Moreover, when it happens, your input seems to be empty… it’s confusing

Solution

Make the clear filters button cancel next debounced search query

Copy link
Member

@fzaninotto fzaninotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only reviewed documentation so far

docs/AutoSubmitFilterForm.md Outdated Show resolved Hide resolved
docs/AutoSubmitFilterForm.md Outdated Show resolved Hide resolved
docs/FilterList.md Outdated Show resolved Hide resolved
docs/FilterList.md Outdated Show resolved Hide resolved
docs/FilterList.md Outdated Show resolved Hide resolved
docs/FilterList.md Outdated Show resolved Hide resolved
docs/FilterList.md Outdated Show resolved Hide resolved
docs/FilterLiveSearch.md Outdated Show resolved Hide resolved
docs/FilterLiveSearch.md Outdated Show resolved Hide resolved
Copy link
Member

@fzaninotto fzaninotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

const {
debounce = 500,
resolver,
validate,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this isn't a bug

packages/ra-core/src/controller/list/filter/index.ts Outdated Show resolved Hide resolved
@slax57
Copy link
Contributor Author

slax57 commented Nov 20, 2024

Problem

With a FilterLiveSearch or an AutoSubmitFilterForm. If I write something and click on the clear filters button faster than my debounce, the new search query happens after the clearing.

It can easily happens when your app need to increase a bit the debounce.

Moreover, when it happens, your input seems to be empty… it’s confusing

Solution

Make the clear filters button cancel next debounced search query

Reproduced.

However, I don't see how I could fix it. The Clear Filters button is outside the AutoSubmitFilterForm, and has no way of canceling the debounced event.

Fortunately, this really an edge case, the default debounce of 500 ms makes it rather unlikely to happen, and clicking the Clear Filters button a second time (or navigating away and back) fixes all issues.

@slax57 slax57 changed the title Introduce <AutoSubmitFilterForm> Introduce <FilterLiveForm> Nov 20, 2024
// Whenever callback or delay changes, we need to update the debounced callback
useLayoutEffect(() => {
debouncedCallbackRef.current = debounce(stableCallback, delay);
}, [stableCallback, delay]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the stableCallback dependency anymore: it's a ref so it will never change

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it's because ESLint is not aware that stableCallback is a ref. NVM then

@djhi djhi merged commit 2ba1258 into next Nov 20, 2024
15 checks passed
@djhi djhi deleted the AutosubmitForm branch November 20, 2024 15:46
@djhi djhi added this to the 5.4.0 milestone Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFR Ready For Review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants