Uh oh!
There was an error while loading. Please reload this page.
RFC: Slots - #223
Conversation
I'm very interested in this proposal, being able to build your own node tree and have it managed by react is very powerful. The string-name based API makes this hard to impossible to statically type. This is something the context API gets right but I feel is missing in this proposal. Do you have any thoughts on this? Maybe a const{Host, Slot}=createSlot() |
@tom-sherman for typings, you will have to do Regarding |
f2e09b7 to
cb90170CompareThe issue with the string-based API was that there is no easy way to say that a string ID relates to a particular slot type. My proposed API was just an example to demonstrate the concept of having the callsite and the definition site be statically linked which your new API also does but in a different way 👍 It would be good if you could add some examples that address the following scenarios:
|
@tom-sherman Added |
here is the demo to build a tree from Slots, with |
stefee
commented
Aug 25, 2022
This is an interesting proposal. I'm a relatively uninformed reader of this, so perhaps an ignorant question: I'm trying to understand what is enabled by this that is not possible using props. For example: functionMyComponent({ firstEl, lastEl }){return<div>{firstEl}<p>Hello</p>{lastEl}</div>;}The 'slots' here are passed as React elements into the component as props. |
@stefee This is a completely legitimate confusion, I think the RFC needs to improve a bit on the example side to showcase what problem this solves at a glance. My mental model for this problem space is based around the I think it would be worth bringing this example into this RFC pretty much verbatim. Another important note is that this is an API targeted primarily at library developers. I wouldn't expect to see it used too much in apps. Hopefully this explains it better! |
nihgwu
commented
Aug 25, 2022
@stefee that's a good question, TLDR: yes you can do it and that's the cheapest approach for app devs, but for ui library authors it's not a good API IMO, and the proposal is much powerful, like A11y and virtualisation support. Think about how would you support A11y in your approach? Consume context in each slot? Which is the approach for most libraries like In terms of Virtualisation, you can find more context in this thread, but you can take it as a tree info collector without rendering the tree to real DOM, so it will enable more possibilities. Also this proposal is much closer to the Slots in Web Components and we don't need to use a static prop( |
nihgwu
commented
Aug 25, 2022
@tom-sherman Yes I agree the examples need to be improved, but I'm afraid it will prolong the content a lot if I include the content of https://github.com/reach/reach-ui/tree/dev/packages/descendants#the-problem while I've provide the links there, but probably I should highlight those links to make them more accessible |
Understood, maybe just highlighting the link more in a more prominent position is the way to go. To be clear I wasn't suggesting including the different "options" from that doc, just the problem statement section. I think |
jlarmstrongiv
commented
Apr 4, 2023
Slots are definitely a feature I miss in React, having worked with Vue and Web Components. First-party support for the Slots API would be great! This RFC would avoid the force-updates and enable slots to be used with React Server Components. |
giuseppelt
commented
Jul 18, 2023
Just as proof-of-concept, I wrote a very simple Maybe not the best solution, but just something to add into the mix. |
desko27
commented
Sep 28, 2023
This is an excellent idea. I've been using React for years and I feel like slots could've helped me in a variety of scenarios where it felt too hacky to achieve true composability. It can be done, but it's sometimes not easily, and definitely not clean. I mean, articles like the one from@kentcdodds show the unquestionable benefits of composable components, but trying to build one can make you feel kind of abandoned/unsupported by the framework itself, as if you were going against it. Today I found create-slots and this RFC. I just started using it and I built a clean composable card component in no time. Feels very native and looks so maintainable. Also TS support is excellent. A nice API that solves a real problem. This is the way. TY @nihgwu, I'd love to see this built in React. |
Flammae
commented
Nov 22, 2023
As someone who independently developed my solution for integrating slots into React even before discovering this RFC, I would like to discuss my implementation here to bring a fresh perspective and talk about some other challenges with this and my own design. My implementation focuses on a simplistic and type-safe API, closely resembling implementations from other frameworks, especially Vue.js. ImplementationtypeListItemProps={children: SlotChildren<|Slot<"title">// Shorthand of Slot<"title", {}>|Slot<"thumbnail">// Shorthand of Slot<"thumbnail", {}>|Slot<{isExpanded: boolean}>// Shorthand of Slot<"default", {isExpanded: boolean}>>;};functionListItem({ children }: ListItemProps){const{ slot, hasSlot }=useSlot(children);// Slot and hasSlot object inferred from children.return(<li>{/* Render thumbnail if provided, otherwise nothing*/}<slot.thumbnail/><div>{/* Render a fallback if title is not provided*/}<slot.title>Expand for more</slot.title>{/* Render the description and pass the prop up to the parent */}<slot.defaultisExpanded={dynamicValue}/></div></li>);}Here, I used a build plugin to change slot elements into function invocations at build time. This is because slots require access to children with closure and thus need to be created for every component. If this feature were built into React, useSlot would not need to accept children as an argument but would be able to access it with a context-like API, eliminating the need for a custom build plugin. Slots could also just be a function, but syntactic sugar is just too good to say no to. Cool Things (So Far):
Drawbacks (So Far):
Usage from parent
|
nihgwu
commented
Nov 23, 2023
@Flammae Thanks for the details comment, will take more time to understand your lib
this is not the case for my impl, I avoided iteration and only rely on React's render mechanism to collect slots, so it just works as long as you don't render extra nodes around slots, so the following still works as tooltip is rendered by constItem=()=><Tooltip><List.Item/></Tooltip>constlist=<List><Item/></List> |
What happens if the tooltip was used this way: <List>{[<Itemkey={1}>First</Item>,<Itemkey={2}>Second</Item>]}{[<Itemkey={1}>Third</Item>,<Itemkey={2}>Fourth</Item>]}<Itemkey={1}>Fifth</Item><Item>Without key</Item></List>And what if the tooltip accepted another slot, Thumbnail, and it was inserted and reordered this way on the subsequent render: <List>{[<Itemkey={2}>Fourth</Item>,<Thumbnailkey={3}/>,<Itemkey={1}>Third</Item>]}<Itemkey={1}>Fifth</Item><Item>Without Key</Item>{[<Itemkey={2}>Second</Item>,<Itemkey={1}>First</Item>,<Itemkey={3}>New Item</Item>]}</List>Would it be able to correctly identify which nodes to update and which nodes to re-render? The correct solution should do this:
Also, if our goal is to not break existing React logic, the items that were previously inside the array and on the subsequent render were outside, or nested deeper, should re-render even if the keys are the same. To clarify, by re-render I mean unmount and remount Edit:I was wrong in thinking React maintains key equality when arrays themselves change positions. What's important is to maintain key equality when arrays stay in the same position and items within the array move. This implementation definitely handles Key equality right!: Here's the test: (npm run test) |
dante01yoon
commented
May 31, 2024
Do we have any further discussion so far? |
dante01yoon
commented
May 31, 2024
Or any plan to implement this in upcoming React release (19, 20..?) |
dante01yoon
commented
Jun 1, 2024
@will-stone do you have any reason just put thumbs down? |
will-stone
commented
Jun 1, 2024
@dante01yoon There will be lots of people subscribed to this thread, and you just pinged them all, thrice. It’s generally best not to ask for updates on these things, as updates will be posted here when they are ready. Asking for updates is like posting “+1” and doesn’t benefit the discussion. Thanks 🙂 (and no need to reply to this message, which will notify everyone again 😅). |
dante01yoon
commented
Jun 2, 2024
Agree in some aspect, but someone may want to know any further discussion happens in somewhere place since discussion suddenly hanged since end of last year. As this didn't mean to spam, IMHO we shouldn't stop those want to ask any updates. Anyways, thanks again for your kindness. |
not sure if this helps anything but I had a rather simple implementation of slots like so: import{typeFunctionComponent,typePropsWithChildren,typeReactNode,useRef}from'react'import{groupBy,mapObjIndexed,path,pipe,prop}from'ramda'importtype{Fn}from'src/contexts/types'exporttypeSlots<Textendsstring>={[SinT]: FunctionComponent<{/**/}>}exporttypeSlotChildren<T>=T&{children: Iterable<ReactNode>}exportconstuseGetSlots: <Textendsstring>(children: Iterable<ReactNode>)=>Slots<T>=pipe(groupBy(path(['type','displayName'])asany),mapObjIndexed((items: ReactNode[])=>()=>items[0])asFn<Slots<any>>,useRef,prop('current')asFn<Slots<any>>)constslotFactory=newProxy({},{get<Textendsstring,PextendskeyofSlots<T>&string>(_target: object,p: P,_receiver: any): Slots<T>[P]{constcomp=({ children }: PropsWithChildren<Parameters<Slots<T>[P]>>)=><>{children}</>comp.displayName=preturncompasSlots<T>[P]}})exportconstuseSlots=<Textendsstring>(): Slots<T>=>slotFactoryasSlots<T>which can then be used like so in the parent component: const{ Header, Main, Side }=useSlots<SidePanelPageSlots>()return(<SidePanelPage><Header><div>Whatevercontentcangohere</div></Header><Main>Main</Main><Side>Side</Side></SidePanelPage>)and SidePanelPage is then defined like so: exporttypeSidePanelPageSlots='Header'|'Main'|'Side'exportconstSidePanelPage=({ mainTitle, sideTitle, children }: SlotChildren<OwnProps>)=>{const{ Header, Main, Side }=useGetSlots<SidePanelPageSlots>(children)return(<div><divclassName="header"><Header/></div><divclassName="main"><Main/></div><divclassName="side"><Side/></div></div>)}since |
amadsen
commented
Nov 4, 2025
I think the React 19 documentation of I've built my own slots implementation that does this by:
Internally the SlotContainer uses a slot specific context - provided by the Slotted component - to dispatch its children (originally synchronously, via useSyncExternalStore) to the associated SlotPosition component. This worked for React 19.1, both for SSR and client rendering. Then React 19.2 changed server side rendering internals to be more like the client and I had to introduce an async server component that does nothing more than resolve a promise - conditionally, based on the existence of a request context - to ensure that children, and therefore SlotContainers, completed rendering before the SlotPosition components. However, React 19.2 also surfaced an error complaining about the synchronous dispatch causing a rerender of the SlotPosition Component during the render of another component. Using queueMicrotask (or If there is or was some way to mark a render during hydration as dependent on the results of its children, or as transitional (but apparently in some way not the same as startTransition) and therefore not the final hydration state it would allow for what I think is a very nice hoisting slot API. |
amadsen
commented
Nov 4, 2025
A |
In this RFC, we propose a way to support Slots pattern in React
View formatted RFC
Here is a demo how it would work I implemented with
react-call-returnin 10 lines