-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Simplify the development of custom List views #4952
Conversation
…aelish operator" This reverts commit 4034017.
Adds filtering capabilities to the ReferenceManyField Slight BC breaks: - useListController no longer returns the version - useReferenceManyFieldController returns a basePath instead of a referenceBasePath
} = props; | ||
if (React.Children.count(children) !== 1) { | ||
throw new Error( | ||
'<ReferenceManyField> only accepts a single child (like <Datagrid>)' | ||
); | ||
} | ||
const { sort, setSortField } = useSortState(initialSort); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this logic has moved to useReferenceManyFieldController
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty neat! Several things should have been done in dedicated PRs though (Datagrid conversion to TS, Reference fields migration to ListContext)
And demonstrate how to do a custom layout in TagList
- use List Context in ExportButton, ListActions, Filter - introduce ListBase - merge permanentFilter with Filter in ListParams to avoid having to do it several times and passing the permanent filters around - use ListBase in TagList and CommentList
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💪
Now ready for review! |
@@ -965,15 +963,15 @@ By default, the filter form doesn't provide a submit button, and submits automat | |||
|
|||
React-admin doesn't provide any component for that, but it's a good opportunity to illustrate the internals of the `<Filter>` component. We'll actually provide an alternative implementation to `<Filter>`. | |||
|
|||
As explained earlier, `<List>` clones the `filters` element twice. It passes special props to the element to let it interact with the URI query parameter more easily: | |||
As explained earlier, `<List>` clones the `filters` element twice - once to display the filter button, and once to display the filter form. This `filters` element can use the `useListContext` hook to interact with the URI query parameter more easily. the hook returns the following constants: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constants?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, these are constants, not variables or props. How would you name them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it confusing because it returns functions as well
Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💪
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ramda part is optional :)
packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts
Outdated
Show resolved
Hide resolved
packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts
Outdated
Show resolved
Hide resolved
}, [setSelectedIds]); | ||
|
||
// filter logic | ||
const [displayedFilters, setDisplayedFilters] = useSafeSetState<{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the useFilterState
hook already allow to do that ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this hook doesn't handle the complex logic of setting filters, displayed filters, and page at the same time, so I can't use it in this case.
packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts
Outdated
Show resolved
Hide resolved
if (!loaded) return; | ||
let finalData = data; | ||
// 1. filter | ||
Object.keys(filterValues).forEach( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to start form the data, with a data.filter
that would then check all filter values.
This way no need to duplicate data.
Additionally, you could use ramda whereEq to filter data
https://ramdajs.com/docs/#whereEq
I checked, ramda is already in react-admin dependency (well in yarn.lock actually)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL ramda.wherEq
Yes, it would prevent iterating several times over the data for multiple filters. BUT Ramda isn't a direct dependency, it's significantly harder to code, and I doubt many people will download very large arrays and do multicriteria search on them. And when it's the case, we'll investigate your optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that hard to code:
const tempData = data.filter(record =>
Object.entries(filterValues)
.all(([filterName, filterValue]) => filterValue == get(record, filterName)),
);
Or even better
const matchFilter = filterValues => record => Object.entries(filterValues)
.all(([filterName, filterValue]) => filterValue == get(record, filterName));
// later
const tempData = data.filter(matchFilter(filterValues));
And easier to understand IMO
); | ||
// 2. sort | ||
if (sort.field) { | ||
finalData = finalData.sort((a, b) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ramda has a sortBy function https://ramdajs.com/docs/#sortBy
Just saying :)
packages/ra-core/src/controller/field/useReferenceManyFieldController.ts
Outdated
Show resolved
Hide resolved
packages/ra-core/src/controller/field/useReferenceManyFieldController.ts
Outdated
Show resolved
Hide resolved
}, [setSelectedIds]); | ||
|
||
// filter logic | ||
const [displayedFilters, setDisplayedFilters] = useSafeSetState<{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useFilterState ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this hook doesn't handle the complex logic of setting filters, displayed filters, and page at the same time, so I can't use it in this case.
if (!loaded) return; | ||
let finalData = data; | ||
// 1. filter | ||
Object.keys(filterValues).forEach( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that hard to code:
const tempData = data.filter(record =>
Object.entries(filterValues)
.all(([filterName, filterValue]) => filterValue == get(record, filterName)),
);
Or even better
const matchFilter = filterValues => record => Object.entries(filterValues)
.all(([filterName, filterValue]) => filterValue == get(record, filterName));
// later
const tempData = data.filter(matchFilter(filterValues));
And easier to understand IMO
Problem
Solution
Use a context to store controller props instead of cloning children and passing them the prop.
Continue cloning the child and passing controller props for now, to keep BC, but ignore these props in child components.
Target
No need to "push" the props from parent to child, the children can "pull" the props from the context.
Tasks
ListContext
anduseListContext
to store and read list controller variables<ListBase>
to simplify the combination of<ListContext>
anduseListController
<List>
and<ListView>
to TypeScript<ListContext.Provider>
Turn(do the layout you want with<ListView>
into a top/right/bottom/left/center layoutListBase
)useListContext
instead of props in PaginationuseListContext
instead of props in FilteruseListContext
instead of props in EmptyuseListContext
instead of props in BulkActionsToolbaruseListContext
instead of props in ListToolbaruseListContext
instead of props in DatagriduseListContext
instead of props in SingleFieldListuseListContext
instead of props in SimpleListuseListContext
instead of props in ArrayFielduseListContext
instead of props in ReferenceManyField (and add filtering capabilities)useListContext
instead of props in ReferenceArrayField (and add sorting, pagination and filtering capabilities)useListContext
instead of props in ListGuesserSlight BC breaks: