Skip to content

Commit 521c2c9

Browse files
committed
remove string based slot name
1 parent 31f5bff commit 521c2c9

File tree

1 file changed

+61
-79
lines changed

1 file changed

+61
-79
lines changed

text/0000-slots.md

+61-79
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,44 @@
44

55
# Summary
66

7-
A way to support slots pattern in React, works similar to [Slots for Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)
8-
but more powerful as we can interpolate slots.
7+
A way to support slots pattern in React, works similar to [Slots for Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots) but more powerful as we can interpolate slots.
98

109
# Basic example
1110

12-
In this example we introduced two method called `createHost` and `createSlot`.
11+
In this example we introduced two methods called `createHost` and `createSlot`.
1312

14-
`createSlot` creates a component won't be rendered to real DOM but collect props.
13+
`createSlot` creates a component which won't be rendered to real DOM but only collect node info.
1514

16-
`createHost` mounts the children included slotted components and read the
17-
collected slots props, and render the result to real DOM conditionally.
15+
`createHost` mounts the children which hosting slotted components and read the collected slots info, and render the result to real DOM conditionally.
1816

1917
```jsx
2018
import { createHost, createSlot } from "react-create-slots";
2119

20+
const TextFieldLabel = createSlot();
21+
const TextFieldInput = createSlot();
22+
const TextFieldTag = createSlot();
23+
2224
const Tag = (props) => <span {...props} />;
2325

2426
const TextField = (props) => {
2527
const id = useId();
2628

27-
return createHost(props.children, (Slots) => {
28-
const labelProps = Slots.get("label");
29-
const inputProps = Slots.get("input");
30-
const tagPropsList = Slots.getAll("tag");
29+
return createHost(props.children, (slots) => {
30+
// library author can create utlis to reduce the boilerplate
31+
const labelSlot = slots.findLast((slot) => slot.type === TextFieldLabel);
32+
const inputSlot = slots.findLast((slot) => slot.type === TextFieldInput);
33+
const tagSlots = slots.filter((slot) => slot.type === TextFieldTag);
3134

3235
return (
3336
<div>
34-
{labelProps && <label htmlFor={inputProps.id ?? id} {...labelProps} />}
35-
<input id={id} {...label} />
36-
{tagPropsList.length > 0 && (
37+
{labelSlot && (
38+
<label htmlFor={inputSlot?.props.id ?? id} {...labelSlot.props} />
39+
)}
40+
<input id={id} {...inputSlot?.props} />
41+
{tagSlots.length > 0 && (
3742
<div>
38-
{tagPropsList.map((tagProps, index) => (
39-
<Tag data-index={index} {...tagProps} />
43+
{tagSlots.map((tagSlot, index) => (
44+
<Tag key={index} data-index={index} {...tagSlot.props} />
4045
))}
4146
</div>
4247
)}
@@ -45,10 +50,6 @@ const TextField = (props) => {
4550
});
4651
};
4752

48-
const TextFieldLabel = createSlot("label");
49-
const TextFieldInput = createSlot("input");
50-
const TextFieldTag = createSlot("tag");
51-
5253
export default function App() {
5354
return (
5455
<div>
@@ -65,98 +66,79 @@ export default function App() {
6566
6667
# Motivation
6768
68-
## Composite component in a configure manor
69+
## Composite component in a configure manner
6970
70-
When creating UI library, we have different approaches on the component
71-
API design. The most common way is the Configuration pattern used by
72-
most UI libraries, everything in one component and provides child props
73-
for composition, like `<TextField label helperText />`. It's easy to use,
74-
but when we need to to add some extra props to label like `data-testid`,
75-
then we have to introduce new props for that which will bloat the api
76-
very easily.
71+
When creating UI library, we have different approaches on the component API design. The most common way is the Configuration pattern used by most UI libraries, everything in one component and provides child props for composition, like `<TextField label helperText />`. It's easy to use, but when we need to to add some extra props to label like `data-testid`, then we have to introduce new props for that which will bloat the api very easily.
7772
78-
Another way to solve this problem is Composition pattern, like
79-
`<TextField><TextFieldLabel /><TextFieldInput /></TextField>`,
80-
it provides the best flexibility, we are free to customise every
81-
part of our component, but it causes another problem: consistency,
82-
we have to organise your sub components exactly same order as expected,
83-
and the biggest problem is that it's harder to communicate between
84-
parent and children.
73+
Another way to solve this problem is Composition pattern, like `<TextField><TextFieldLabel /><TextFieldInput /></TextField>`, it provides the best flexibility, we are free to customise every part of our component, but it causes another problem: consistency, we have to organise your sub components exactly same order as expected, and the biggest problem is that it's harder to communicate between parent and children.
8574
86-
For a dedicated Design System, we need consistent ui regardless how
87-
we compose it.
75+
For a dedicated Design System, we need consistent ui regardless how we compose it.
8876
89-
Here the Slots pattern solve those problem perfectly, we can compose
90-
our component with both flexibility and consistency, and it's extreme
91-
easy to add A11y support thanks the ability of Inversion of Control.
77+
Here the Slots pattern solve those problem perfectly, we can compose our component with both flexibility and consistency, and it's extreme easy to add A11y support thanks the ability of Inversion of Control.
9278
93-
## Accessible List
79+
## Accessible List and Virtualisation
9480
95-
Quoted from [the comment](https://github.com/facebook/react/issues/24979#issuecomment-1193176328)
96-
by @devongovett
81+
Quoted from [the comment](https://github.com/facebook/react/issues/24979#issuecomment-1193176328) by @devongovett
9782
9883
> A bunch of libraries have a problem where they need to know about certain types of descendants. For example, a list component with keyboard navigation needs to know what elements exist in the collection in order to implement things like typeahead, arrow keys, selection, etc. Reach UI has a good [overview](https://github.com/reach/reach-ui/tree/dev/packages/descendants) of a bunch of different approaches to this. The most commonly used of them involve rendering all of the items to the DOM, and using some kind of context-based registration system to tell the parent about themselves, and the DOM to sort them into the correct order.
9984
> This has the downside that all of the items must be in the DOM at all times. In some cases, like virtualized scrolling, or a combobox/select where users can set the item without showing the list, some or all of the items shouldn't be rendered to the DOM. In React Aria, we walk the JSX tree to do this, which makes for a more natural API than giving up JSX completely (info). But this breaks composition, because only certain known element types are allowed.
10085
101-
Even with the Reach UI's solution, it doesn't work well with SSR.
102-
With the Slots pattern, we don't render the children to real DOM but
103-
only collect information, so virtualisation is supported by nature.
86+
Even with the Reach UI's solution, it doesn't work well with SSR. With the Slots pattern, we don't render the children to real DOM but only collect information, so virtualisation is supported by nature.
10487
10588
## Why we want it built in core
10689
107-
I've researched a lot different approaches, none of the current solutions
108-
work perfectly without any drawbacks, my [solution](https://github.com/nihgwu/create-slots)
109-
is the closest one. But as @devongovett pointed out [here](https://github.com/facebook/react/issues/24979#issuecomment-1205909188), `react-reconciler` is designed to do this job,
110-
It would be nice to see it in core, like `react-call-return`.
90+
I've researched a lot different approaches, none of the current solutions work perfectly without any drawbacks, my [solution](https://github.com/nihgwu/create-slots) is the closest one. But as @devongovett pointed out [here](https://github.com/facebook/react/issues/24979#issuecomment-1205909188), `react-reconciler` is designed to do this job, It would be nice to see it in core, like `react-call-return`.
11191
112-
# Detailed design
92+
It seems `React.Children.forEach` can do the same job demoed in the example above, but there are a lot limitations which are well documented [here](https://github.com/reach/reach-ui/tree/dev/packages/descendants), with this proposal we are free to extend the slot and compose in any way, e.g.
93+
94+
```jsx
95+
const StyledLabel = styled(TextField)``;
96+
97+
const PartialTextField = ({ show }) => (
98+
<>
99+
{show && <StyledLabel>Dynamic styled label</StyledLabel>}
100+
<>
101+
<TextFieldInput />
102+
</>
103+
</>
104+
);
113105

114-
The api is very similar to the [deleted](https://github.com/facebook/react/pull/12820)
115-
experimental package [`react-call-return`](https://github.com/facebook/react/pull/11364),
116-
but with a simpler mental model.
106+
export default function App() {
107+
return (
108+
<TextField>
109+
<PartialTextField />
110+
</TextField>
111+
);
112+
}
113+
```
114+
115+
# Detailed design
117116
118-
`createSlot(slotName)` creates a slot component and won't be rendered to
119-
real DOM but only collect props, which will be used by `createHost`
117+
The api is very similar to the [deleted](https://github.com/facebook/react/pull/12820) experimental package [`react-call-return`](https://github.com/facebook/react/pull/11364), but with a simpler mental model.
120118
121-
`createHost(children, callback)` mounts the children included slotted
122-
components and read the collected slots' props, and render the result to
123-
real DOM conditionally. The argument of `callback` provides the following
124-
methods:
119+
`createSlot<SlotProps>()` creates a slot component and won't be rendered to real DOM but only collect node info, which will be used by `createHost`, for TS/Flow users, `SlotProps` is the props we support in each slot, will be `LabelProps`, `InputProps`, `TagProps` for the example
125120
126-
- `get(slotName: string)` returns the registered slot's props by name,
127-
will return the last registered named slot if the host expect only one
128-
but consumer provides more.
129-
- `getAll(slotName: string)` returns all the registered slots as array
130-
this method is useful to create a list.
131-
- `getChildren()` returns the rest of children without slots, e.g.
132-
`<Host>rest<Slot /></Host>` it will return `rest`.
121+
`createHost(children: JSX.Element, callback: (slots: React.Element) => JSX.Element | null)` mounts the children which hosting slotted components and read the collected slots elements, and render the result to real DOM conditionally. We can find the slots by filter the `slots`(shown in the example above), similar to Web Components, there could be nodes not wrapped in slots at all will be treated as unnamed slot(`<slot></slot>)`
133122
134123
# Drawbacks
135124
136-
Even we are going to support this feature in a separate package or entry,
137-
it will still increase the bundle size of React a bit as we need core support.
125+
Even we are going to support this feature in a separate package or entry, it will still increase the bundle size of React a bit as we need core support.
138126
139127
# Alternatives
140128
141-
- Bring back `react-call-return` which also could be used to implement this
142-
feature, but the mental model is hard to understand.
143-
- Leave it to userspace to implement with current api, like [create-slots](https://github.com/nihgwu/create-slots), but not very efficient and have
144-
some drawbacks, e.g. unable to catch children not wrapped in slots.
129+
- Bring back `react-call-return` which also could be used to implement this feature [demo](https://codesandbox.io/s/long-hill-os6msf?file=/src/App.js), but the mental model is hard to understand.
130+
- Leave it to userspace to implement with current api, like [create-slots](https://github.com/nihgwu/create-slots), but not very efficient and have some drawbacks, e.g. unable to catch children not wrapped in slots, for list slots we have to force update to get the correct index.
145131
146132
# Adoption strategy
147133
148-
There is no breaking changes, it's a new feature more for UI library authors
149-
instead of application developers.
134+
There is no breaking changes, it's a new feature more for UI library authors instead of application developers.
150135
151136
# How we teach this
152137
153-
Slots pattern is a native feature of Web Components, other popular frameworks
154-
like Vue and Svelte also provide similar concepts. So the concept itself is
155-
easy to understand, we only have to document the api.
138+
Slots pattern is a native feature of Web Components, other popular frameworks like Vue and Svelte also provide similar concepts. So the concept itself is easy to understand, we only have to document the api.
156139
157140
# Unresolved questions
158141
159142
- Finalise the namings
160-
- Do we need to provide internal key for list rendering, though I don't see it
161-
in `react-call-return`
143+
- Do we need to provide internal key for list rendering?
162144
- Adding to a new package or to `React` directly

0 commit comments

Comments
 (0)