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

[RFC] The way to override components and slots #34334

Open
siriwatknp opened this issue Sep 16, 2022 · 18 comments
Open

[RFC] The way to override components and slots #34334

siriwatknp opened this issue Sep 16, 2022 · 18 comments
Assignees
Labels
package: joy-ui Specific to @mui/joy package: material-ui Specific to @mui/material RFC Request For Comments

Comments

@siriwatknp
Copy link
Member

siriwatknp commented Sep 16, 2022

What's the problem? 🤔

Before reading this RFC, please go through #34333 first so that we are on the same page about the meaning of components and slots.

From #33416, and #21453 it seems like we are only talking about slot override (replacing the slot). However, for Material UI and Joy UI, we also need a component override (changing the HTML tag of the slot).

What are the requirements? ❓

  • How to override the HTML tag on each slot
  • How to replace the slot with a custom React component

Proposed solution 🟢

My proposed solution aims for the least breaking changes. All components (MUI Base, Material UI, and Joy UI) should follow this:

Components with a single slot (root)

  • component (existing prop): for changing the root slot's HTML tag. (I think we should not drop this prop because it will be a huge breaking change)

For this kind of components, it does not make sense to replace the root slot so having just component prop is cleaner. This is not true for MUI Base 🤔.

Components with more than one slot

  • component (existing prop): for changing the subcomponent of the root slot (could be an HTML tag or React element).
  • slots and slotsProps same as [RFC] Rename of the components prop to slots #33416
  • slots.{slot}.component: for changing the subcomponent of the target slot (could be an HTML tag or React element).

To replace the HTML of the slot, use slotsProps={{ listbox: { component: 'div' } }}.

Components that have nested components

See the problem and another example

Flatten the nested component slots with a new name. For example, TextField has Input as a nested component could look something like this:

<TextField
  slotsProps={{
    root: {...},
    inputRoot: { ... },
    inputInput: { ... },
    helperText: { ... }
  }}
/>
@siriwatknp siriwatknp added the RFC Request For Comments label Sep 16, 2022
@siriwatknp siriwatknp changed the title [RFC] The way to override components and slots meaning [RFC] The way to override components and slots Sep 16, 2022
@michaldudak
Copy link
Member

So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.x.component, yes?

@siriwatknp
Copy link
Member Author

So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.x.component, yes?

hmm, I forgot that we can use component inside slotsProps:

<Select
  slotProps={{
    listbox: {
      component: 'div',
    }
  }}
/>

This sounds better than having another components prop.

@michaldudak
Copy link
Member

Generally, I see two changes are proposed here:

  1. Change (or settle on) the interpretation of the component prop, so that it is used to customize the leaf element (similarly to the as prop).
  2. Introduce the components prop that would act as a shortcut to slotProps.x.component.

Do I understand the intent correctly?

Assuming so, this proposal would only make sense in styled libraries (Material UI and Joy UI). In MUI Base, as explained in #34333 (comment), the component is almost exclusively a DOM node, so it does not have the as prop.
That being said, if we decide to go with option 1 I listed above, the component prop would be unnecessary in MUI Base (as was already proposed in #28189). We could leave it as is (as an alias for slots.root), but it would have a different meaning in styled vs unstyled libraries.

@mnajdova
Copy link
Member

Just clarifying that I understand the proposal correctly. We would have slots and slotsProps in Mui Base, Material UI and Joy UI, that would behave the same (replace completely the slot that is being rendered). For e.g.:

const Root = slots.root ?? DefaultRootComponent;

Apart from this, in the components inside Material UI and Joy UI, there would be a component prop which is basically an alias for the as prop in emotion (so that when it is used, all styles that were previously defined will still be applied). As all slots would be compossible components, we should already have the support for the component prop there, so we should need only one component prop per component.

We don't really need this in Mui Base, as the behavior is the same as slots.root as we don't have any styles, but that is a different discussion, already linked in #34334 (comment).

Btw, this is how it already works in Material UI, so I don't expect big change anyway, unless I am misunderstanding the proposal.

@siriwatknp
Copy link
Member Author

siriwatknp commented Sep 20, 2022

so we should need only one component prop per component.

Yes, the component prop can be considered as a shortcut for slotsProps.component. If both are provided, slotsProps.component should have higher priority. cc @michaldudak

Btw, this is how it already works in Material UI, so I don't expect big change anyway, unless I am misunderstanding the proposal.

I'd say that I expect big change for Material UI because most components are using another pattern. e.g. Accordion has TransitionComponent and TransitionProps. This will be breaking changes:

// current
<Accordion TransitionComponent={Slide} TransitionProps={{ delay: 100 }} />

// new
<Accordion slots={{ transition: Slide }} slotsProps={{ transition: { delay: 100 } }} />

@siriwatknp siriwatknp added package: material-ui Specific to @mui/material package: joy-ui Specific to @mui/joy labels Sep 20, 2022
@siriwatknp
Copy link
Member Author

Marked this as RFC for Material UI and Joy UI only.

@mnajdova
Copy link
Member

I'd say that I expect big change for Material UI because most components are using another pattern. e.g. Accordion has TransitionComponent and TransitionProps. This will be breaking changes:

// current
<Accordion TransitionComponent={Slide} TransitionProps={{ delay: 100 }} />

// new
<Accordion slots={{ transition: Slide }} slotsProps={{ transition: { delay: 100 } }} />

What I meant is that in terms of behavior it will behave the same. For the props surface, we could support both for smoother migration and provide codemods if people want to migrate to the new paradigm sooner.

@michaldudak
Copy link
Member

What I meant is that in terms of behavior it will behave the same.

Not necessarily. The current pattern in Material UI uses the as prop to modify the leaf component:

const AutocompletePaper = styled(Paper, { /* ... */ });

/* ... */

<AutocompletePaper
  ownerState={ownerState}
  as={PaperComponent}
  {...componentsProps.paper}
  className={clsx(classes.paper, componentsProps.paper?.className)}
>

whereas the slots prop would replace the whole slot (unless it has a different behavior in styled vs unstyled libraries).

@siriwatknp
Copy link
Member Author

siriwatknp commented Sep 20, 2022

For the props surface, we could support both for smoother migration and provide codemods if people want to migrate to the new paradigm sooner

Yep, agree. We can start introducing slots and slotsProps in v5 with a codemod and then deprecate the existing props, e.g. TransitionComponent.

@mnajdova
Copy link
Member

whereas the slots prop would replace the whole slot (unless it has a different behavior in styled vs unstyled libraries).

Yep, component props is "as" and the slot props behave the same as the component props we have on some components, for example TransitionComponent, PopperComponent etc.

@siriwatknp
Copy link
Member Author

Update: Joy UI already follows this approach.

@siriwatknp
Copy link
Member Author

This issue can be closed once all Material UI components support slots and slotProps. cc @DiegoAndai

@DiegoAndai DiegoAndai self-assigned this Jan 17, 2024
@DiegoAndai DiegoAndai added this to the Material UI: v6 milestone Jan 17, 2024
@colangeloproductions
Copy link

Hi there,
I've been trying to understand the possibilities of the slots and slotProps with help of:

But I'm not sure its capable of what I'm trying to do.. maybe someone could give some advice?

I have a React Component thats based on the MUI Base Input -> which is InputCalculate (Math operations inside of Input field)

Now with the NumberInput that has been released I've been trying to use the slots prop to render my InputCalculate instead of the default.

Can anyone advise me how I can render a complete different HTML structure / Component and also give it props? The docs I've read are giving me the impression I can only change the HTML Tag (ol to ul, button to a etc.) and css styles.

Cheers
Romeo

@michaldudak
Copy link
Member

You can pass in props using the slotProps prop. Unfortunately, due to performance issues with TypeScript, if you pass in a custom slot component, the corresponding slotProps won't have a related type. So if you do slots={{ root: MyFancyComponent }}, the slotProps.root won't expect the props of MyFancyComponent and some casting may be necessary.
We are aware of the limitation of this API, and are currently working on improvements in this area. We plan to post an RFC soon and flesh out all the details of the new API.

@colangeloproductions
Copy link

Okay thats some clarity, but what about the InputCalculate that I would like render instead of the input slot from the NumberInput?

@michaldudak
Copy link
Member

What exactly do you have a problem with?

This should work:

<NumberInput slots={{ input: InputCalculate }} />

@poojaphapale
Copy link

@michaldudak
How can we pass parameters to InputCalculate
<NumberInput slots={{ input: InputCalculate }} />

@michaldudak
Copy link
Member

You can use slotProps:

<NumberInput slots={{ input: InputCalculate }} slotProps={{ input: { ... } }} />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: joy-ui Specific to @mui/joy package: material-ui Specific to @mui/material RFC Request For Comments
Projects
Status: In progress
Development

No branches or pull requests

8 participants