Introduction

Slots in React offer a powerful way to pass elements to a component and specify where these elements should be rendered. In this article, we explore different strategies for implementing React slots, drawing inspiration from various sources.

About Slots

Slots, in general, allow us to pass elements to a component and dictate where these elements should be rendered. React, although lacking an official concept of named slots, provides flexibility through JSX. Let’s delve into various approaches to implementing react slots.

Default React Slots (Children)

The default slot in React slots is referred to as the children property. Whatever is placed between the opening and closing tags of a component becomes the children property for that component and serves as the default slot.

<Card>
  {/* Default react slots passed as `children` property */}
  <p>Content</p>
</Card>

Multiple Slot Props

Instead of passing elements between the tags, you can directly pass the children property itself.

<Card children={<p>Content</p>} />

For named react slots, we can define additional props to work as named slots:

<Card header={<h1>Title</h1>} content={<p>Content</p>} footer={<a href="#">Read more</a>} />

Compound Components

Compound components involve splitting a component into multiple components, such as CardCardHeader, and CardFooter. These act similarly to named react slots.

<Card>
  <CardHeader>
    <h1>Title</h1>
  </CardHeader>
  <p>Content</p>
  <CardFooter>
    <a href="#">Read more</a>
  </CardFooter>
</Card>

Slots by Type

Creating react slots based on the type of elements passed as children involves understanding how JSX is compiled.

<Card>
  <CardHeader>
    <h1>Title</h1>
  </CardHeader>
  <p>Content</p>
  <CardFooter>
    <a href="#">Read more</a>
  </CardFooter>
</Card>

Generic Slot Component

We can adapt the slot element of WebComponents in React by creating a generic, reusable Slot component that accepts a name property.

<Card>
  <Slot name="header">
    <h1>Title</h1>
  </Slot>
  <p>Content</p>
  <Slot name="footer">
    <a href="#">Read more</a>
  </Slot>
</Card>

React Slots with Context API

Certainly! In React, the Context API is used to pass data through the component tree without having to pass props manually at every level. React Slots, in this context, refer to a way of using Context API to manage dynamic content within a component. Here’s a step-by-step guide with a detailed code example:

  1. Create a Context:
  2. Start by creating a context using React.createContext. This context will hold the data you want to share across components.
   // MyContext.js
   import React, { createContext } from 'react';

   const MyContext = createContext();

   export default MyContext;
  1. Create a Provider Component:
  2. Create a provider component that will wrap your application or a specific part of it. This component will use the Provider from the context and supply the data that you want to share.
   // MyProvider.js
   import React, { useState } from 'react';
   import MyContext from './MyContext';

   const MyProvider = ({ children }) => {
     const [data, setData] = useState('Default Data');

     return (
       <MyContext.Provider value={{ data, setData }}>
         {children}
       </MyContext.Provider>
     );
   };

   export default MyProvider;
  1. Create a Consumer Hook:
  2. To access the data in your components, create a custom hook that uses the useContext hook from React.
   // useMyContext.js
   import { useContext } from 'react';
   import MyContext from './MyContext';

   const useMyContext = () => {
     return useContext(MyContext);
   };

   export default useMyContext;
  1. Use the Provider in your App:
  2. Wrap your application or the part of it where you want to use the shared data with the MyProvider component.
   // App.js
   import React from 'react';
   import MyProvider from './MyProvider';
   import MyComponent from './MyComponent';

   const App = () => {
     return (
       <MyProvider>
         <MyComponent />
       </MyProvider>
     );
   };

   export default App;
  1. Use the Context in your Component:
  2. Finally, in the component where you want to use the shared data, use the custom hook useMyContext to access the data and update it.
   // MyComponent.js
   import React from 'react';
   import useMyContext from './useMyContext';

   const MyComponent = () => {
     const { data, setData } = useMyContext();

     const handleButtonClick = () => {
       // Update the shared data
       setData('New Data');
     };

     return (
       <div>
         <p>{data}</p>
         <button onClick={handleButtonClick}>Update Data</button>
       </div>
     );
   };

   export default MyComponent;

Now, any changes made to the shared data using setData in MyComponent will be reflected in any other component that uses the same MyProvider. This way, you’ve effectively created react slots for dynamic content using the React Context API.

Fake DOM

React Aria introduces an interesting approach using a fake DOM to efficiently update collections.

Here’s a general explanation of how React Aria, in conjunction with virtualization, handles dynamic content and updates:

  1. Virtualization:
  2. React Aria leverages the concept of virtualization, which involves rendering only the items that are currently visible in the user’s viewport. This is particularly important when dealing with large collections, such as lists or grids, to avoid rendering and managing all items at once.
  3. “Fake DOM” or Virtual DOM:
  4. React Aria maintains a virtual DOM, sometimes referred to as a “fake DOM,” in memory. This virtual representation of the DOM contains lightweight representations of UI elements that are not currently visible on the screen. This allows React Aria to efficiently manage and update the UI without the need to render and manipulate the entire collection.
  5. Efficient Updates with Slots:
  6. When dealing with dynamic content or updates, React Aria can efficiently update the slots within its virtual DOM. The concept of “slots” here refers to the placeholders where dynamic content can be inserted. As data changes, React Aria can update only the relevant slots, ensuring a more efficient rendering process.
  7. Accessibility Considerations:
  8. React Aria places a strong emphasis on accessibility, ensuring that the virtualization techniques used do not compromise the accessibility of the components. This includes managing focus, keyboard navigation, and other accessibility features while efficiently updating the UI.
  9. Adaptive Components:
  10. React Spectrum, and by extension React Aria, are designed to create adaptive components that work well across different platforms and devices. This adaptability includes handling various input methods, screen sizes, and accessibility requirements.

Here’s a simplified example to illustrate the usage of React Aria with a virtualized list:

// MyListComponent.js
import React from 'react';
import { useListBox } from 'react-aria';

const MyListComponent = ({ items }) => {
  const { listBoxProps, labelProps } = useListBox();

  return (
    <div {...listBoxProps}>
      {items.map((item) => (
        <div key={item.id}>{item.content}</div>
      ))}
    </div>
  );
};

export default MyListComponent;

In this example, useListBox is part of React Aria and provides the necessary props for managing a listbox, including handling virtualization efficiently.

React Slots RFC

A React Slots RFC is proposed to bring a react slots API to the ecosystem, addressing existing drawbacks.

The React Slots RFC, outlined in Pull Request #223 on the official React GitHub repository, introduces a novel pattern to support slots in React. This proposed enhancement aims to empower developers with a more flexible and intuitive way to structure and manage components, allowing for a dynamic and composable approach.

 function ListItem({ children }: ListItemProps) {
  const { slot, hasSlot } = useSlot(children); // Slot and hasSlot object inferred from children.
  
  // Called for Each node for this slot
  const SlotWithOverride = React.Children.overrideSlot(slot.thumbnail, (node) => {
    // Enforce final node type
    // Add props
    // Change type
  })
 
  return (
    <li>
      {/* Render thumbnail if provided, otherwise nothing*/}
      <SlotWithOverride />
      <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.default isExpanded={dynamicValue} />
      </div>
    </li>
  );
}

Slots in Other Frameworks

We also explore how Vue and Angular solve the component slots problem.

Vue

Vue supports the <slot> element for default and named slots.

Slot Content and Outlet

Components embrace dynamic content through slots. Witness the power:

<FancyButton> Click me!  </FancyButton>

The magic unfolds in the template:

<button class="fancy-btn"> <slot></slot>  </button>

Craft versatile components with a dash of creativity.

Render Scope

Unlock the realm where slot content taps into the parent’s data scope:

<span>—</span> <FancyButton>—</FancyButton>

Behold as expressions dance within their defined scope.

Fallback Content

Prepare for the unexpected by setting default slot content:

<button type="submit"> <slot> Submit  </slot> </button>

When slots run dry, the fallback content takes the stage.

Named Slots

Bestow names upon your component’s slots for precision:

<slot name="header"></slot>, <template #header>

Guide content to its designated sanctuary within the component.

Dynamic Slot Names

Witness the dynamic ballet of directive arguments on v-slot:

<template v-slot:[dynamicSlotName]> ... </template>

Watch the syntax dance in the dynamic spotlight.

Scoped Slots

Forge connections between parent and child scopes:

<slot :text="greetingMessage" :count="1"></slot>

Scoped slots, the virtuosos of component interaction.

Fancy List Example

Craft your masterpiece with scoped slots for dynamic styling:

<template #item="{ body, username, likes }"> ... </template>

A canvas for personalized rendering of list items.

Renderless Components

Delegate the visual symphony through renderless components:

<MouseTracker v-slot="{ x, y }"> Mouse is at: —, — </MouseTracker>

Logic encapsulated, visuals conducted by the audience.

Angular

Angular supports content projection using ng-content and the select attribute.

Unleashing the Power of ng-content

ng-content Demystified:

  • Uncover the capabilities of Angular’s ng-content feature, a robust tool for content projection.
  • Witness how it empowers developers to customize views and presentations uniquely for each use case.

Templating and Directives in Content Projection

Navigating Templates and Directives:

  • Understand how content projection enables the use of templates and directives, allowing the cloning of original content and its dynamic addition to the DOM.
  • Explore the flexibility it introduces in programming Angular components.

Passing HTML Content Dynamically

Dynamic HTML Content Passage:

  • Learn about the method of passing HTML content from parent to child components using content projection.
  • Witness the seamless display of customized content within the child component.

Reusability Through Content Projection

Content Reuse Strategies:

  • Explore content projection as a powerful mechanism for reusing component content in Angular applications.
  • Recognize its broader scope, extending beyond simple data or content passing between parent and child components.

Customizing Components for Varied Use Cases

Tailoring Components for Specific Needs:

  • Dive into scenarios where component customization becomes crucial for varied views or presentations of data.
  • Experience how content projection becomes a facilitator for this customization process.

Global Impact of Content Projection in Angular

Widespread Adoption:

  • Acknowledge the global relevance of content projection within the Angular community.
  • Discover its diverse applications across different scenarios, showcasing its adaptability and versatility.

Resources for In-Depth Learning

Learning Journey in Angular Content Projection:

  • Access a plethora of learning resources, including courses, blogs, and videos, offering comprehensive insights into content projection in Angular.
  • Developers can embark on a journey of exploration to deepen their understanding and gain practical insights.

Conclusion

Each approach has its pros and cons, and the choice depends on your specific use case. We hope that the React Slots RFC will provide a robust API for handling component react slots in the future.

Explore the world of React component slots with our comprehensive guide. Learn different strategies, understand their pros and cons, and stay informed about the latest developments. Enhance your React development skills today!

Building Component Slots in React | Pass Multiple Children to a React Component with Slots | Composition vs Inheritance