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: support Component and Element for List props #10539

Open
wants to merge 4 commits into
base: next
Choose a base branch
from

Conversation

guilbill
Copy link
Contributor

@guilbill guilbill commented Feb 21, 2025

Problem

In order to have a better developper experience we want to simplify the way we customise the List sub-component (eg: actions, filters, aside, pagination).

Solution

Allow to use React.ComponentType in addition to ReactElement.

  const Actions = () => <Box sx={{ backgroundColor: 'info.main' }}>Actions</Box>;
  <List actions={Actions}>
      <BookList />
  </List>

or

    <List
        actions={
            <Box sx={{ backgroundColor: 'info.main' }}>
                Actions
            </Box>
        }
    >
        <BookList />
    </List>

How To Test

Create a List using a component (actions={Actions})

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.

@guilbill guilbill added the WIP Work In Progress label Feb 21, 2025
@guilbill guilbill force-pushed the support-both-element-and-component-for-list branch from 92437f9 to abf98d3 Compare February 21, 2025 15:24
@guilbill guilbill changed the title feat: support Component and Element as actions prop feat: support Component and Element for List props Feb 21, 2025
@guilbill guilbill added RFR Ready For Review and removed WIP Work In Progress labels Feb 24, 2025
@slax57 slax57 self-requested a review February 25, 2025 13:27
@@ -139,6 +139,7 @@ export const PostList = () => (
</List>
);
```
You can use a `ReactElement` (e.g. `actions={<ListActions/>}`), a `ComponentType` (e.g. `actions={ListActions}`) or false to disable it.
Copy link
Contributor

Choose a reason for hiding this comment

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

thought: I'd be tempted to use the component syntax in the example above (since it's shorter and offers better performances), and simply mention that action also accepts the other types

Suggested change
You can use a `ReactElement` (e.g. `actions={<ListActions/>}`), a `ComponentType` (e.g. `actions={ListActions}`) or false to disable it.
**Tip:** `action` also accepts a `ReactElement` (e.g. `actions={<ListActions/>}`), or `false`.

But on the other hand, it may be odd to encourage this syntax only for the List component (since it's the only one we support for now...)

So I'm hesitating.

Same remark applies to other props below.

@fzaninotto @djhi wdyt?

@@ -175,7 +176,7 @@ The default `<List>` layout lets you render the component of your choice on the

![List with aside](./img/list_aside.webp)

Pass a React element as the `aside` prop for that purpose:
Pass a React element or ComponentType as the `aside` prop for that purpose:
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: I believe ComponentType designates the type of such a component, but my understanding is that you can speak of a React Component (and a React Element)

Suggested change
Pass a React element or ComponentType as the `aside` prop for that purpose:
Pass a React element or component as the `aside` prop for that purpose:

Copy link
Contributor Author

@guilbill guilbill Feb 25, 2025

Choose a reason for hiding this comment

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

But it IS expected a ComponentType (as defined in the ListViewProps), otherwise we couldn't do aside={Aside}. It let's the responsibility of the instanciation of the subcomponent (aside) to the component (List).

@@ -483,7 +484,7 @@ When there is no result, and there is no active filter, and the resource has a c

![Empty invite](./img/list-empty.png)

You can use the `empty` prop to replace that page by a custom component:
You can use the `empty` prop to replace that page by a custom component, passing a ReactElement or ComponentType :
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
You can use the `empty` prop to replace that page by a custom component, passing a ReactElement or ComponentType :
You can use the `empty` prop to replace that page by a custom React element or component:

@@ -783,7 +784,7 @@ By default, the `<List>` view displays a set of pagination controls at the botto

![Pagination](./img/list-pagination.webp)

The `pagination` prop allows to replace the default pagination controls by your own.
The `pagination` prop allows to replace the default pagination controls by your own, either with a ReactElement or a ComponentType.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The `pagination` prop allows to replace the default pagination controls by your own, either with a ReactElement or a ComponentType.
The `pagination` prop allows to replace the default pagination controls by your own, either with a React element or component.

@@ -990,7 +991,7 @@ export const PostList = () => (
);
```

The title can be a string, a React element, or `false` to disable the title.
The title can be a string, a React element, a ComponentType or `false` to disable the title.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The title can be a string, a React element, a ComponentType or `false` to disable the title.
The title can be a string, a React element, a React component or `false` to disable the title.

</ThemeProvider>
</CoreAdminContext>
);
expect(screen.queryAllByText('Hello')).toHaveLength(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
expect(screen.queryAllByText('Hello')).toHaveLength(1);
screen.getByText('Hello');

Comment on lines +246 to +249
await waitFor(() => {
expect(screen.queryByText('resources.posts.empty')).toBeNull();
screen.getByText('Custom Empty');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
await waitFor(() => {
expect(screen.queryByText('resources.posts.empty')).toBeNull();
screen.getByText('Custom Empty');
});
await screen.findByText('Custom Empty');
expect(screen.queryByText('resources.posts.empty')).toBeNull();

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Admin, AutocompleteInput } from 'react-admin';
import { Admin, AutocompleteInput, Pagination } from 'react-admin';
Copy link
Contributor

Choose a reason for hiding this comment

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

linter warning at this line

@@ -131,7 +167,7 @@ export interface ListViewProps {
* </List>
* );
*/
actions?: ReactElement | false;
actions?: ReactElement | ComponentType | false;
Copy link
Contributor

Choose a reason for hiding this comment

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

thought: IMO we should use ReactNode instead of ReactElement

Suggested change
actions?: ReactElement | ComponentType | false;
actions?: ReactNode | ComponentType;

I'd go even further and would say that the getElement implementation is currently too restrictive.

According to the React docs:

Note isValidElement checks whether the argument is a React element, not whether it’s a React node. For example, 42 is not a valid React element. However, it is a perfectly valid React node.

So to me, the implementation should be the following:

const getElement = (ElementOrComponent: ComponentType | ReactNode) => {
    if (isValidElementType(ElementOrComponent)) {
        const Element = ElementOrComponent as ComponentType;
        return <Element />;
    }
    return ElementOrComponent;
};

Same applies to other props below.

@fzaninotto @djhi wdyt?

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.

2 participants