React: slots like mom's friend's son

When composing components, the task of customizing the content of a component very often arises. For example, we have a DatePicker component and we need to display different Apply buttons in different parts of the web application.

To solve such problems, every popular technology today uses the concept of "slots". Angular has ngContent , Vue , Svelte and WebComponents have slots. And only the popular React library does not have a full-fledged concept of slots today.

There are several approaches to solving this problem in React:

  1. A component can either render all of its children in its entirety, or "climb" into them through the React.Children API and pointwise manipulate the children

  2. A component can declare so-called renderProps , and render the content returned from them in the right places:

    <MyComponent renderFooter={data => (<h1>Bye, ${data.name}</h1>)}/>

The renderProps approach is generally well known and has no fundamental flaws. Unless it's not very convenient to use it, in comparison with full-fledged slots. NPM has several libraries, such as react-view-slot , but it doesn't seem to me that they are convenient enough and, most importantly, they simply solve the problem.

I decided to try to fix this fatal flaw , and now I'll tell you how.

I see the goal - I don't see the implementation

Before programming anything, it is useful to know which API you want to get. This is how my sketch of the desired result looked like:

const Component = props => {
  Component.NameSlot = useSlot(props.children);

  return (
    <div>
      <h1>
        <Component.NameSlot.Receiver>
          Default value
        </Component.NameSlot.Receiver>
      </h1>

      Hello {props.children}
    </div>
  );
};

function App() {
  return (
    <div>
      Hello!
      <Component>
        Foo
        <Component.NameSlot>
          Bobobo
        </Component.NameSlot>
      </Component>
    </div>
  );
}

The idea was to create the necessary slots and store the function-component in a static property, and then use it appropriately on the sending and receiving sides.

, . – , , , -, . , , API, :

import {createSlot} from 'react-slotify';

export const MySlot = createSlot();

export const Component = ({children}) => {
  return (
    <div>
      This component contains slot:
      
      <MySlot.Renderer childs={children}>
        This is default slot content
      </MySlot.Renderer>
      
      <div>It also renders children: {children}</div>
    </div>
  );
};
import {Component, MySlot} from './component';

const App = () => {
  return (
    <div>
      <Component>
        <MySlot>Slotted content</MySlot>
        Other content
      </Component>
    </div>
  );
};

, API, , –  MySlot, {children}, , MySlot.Renderer. , JS-, :

export function createSlot() {
  const Slot = ({ children, showChildren }) => {
    return showChildren ? children : null;
  }

  const Renderer = ({ childs, children }) => {
    const slotted = React.Children.toArray(childs).find(child => {
      return React.isValidElement(child) && child.type === Slot;
    });

    if (!slotted || !React.isValidElement(slotted)) {
      return children;
    }
    return React.cloneElement(slotted, { showChildren: true });
  };

  Slot.Renderer = Renderer;

  return Slot;
}

-, 20 . , React- , . . –  Slot. , :

export function createSlot() {
  const Slot = ({ children, showChildren }) => {
    return showChildren ? children : null;
  }
  return Slot;
}

, Slot –  , showChildren={true}. , , Slot .

– Renderer. –  -, Slot-, , showChildren={true}:

  const Renderer = ({ childs, children }) => {
    const slotted = React.Children.toArray(childs).find(child => {
      return React.isValidElement(child) && child.type === Slot;
    });

    if (!slotted || !React.isValidElement(slotted)) {
      return children;
    }
    return React.cloneElement(slotted, { showChildren: true });
  };

, Renderer , , Slot . .

–  Renderer Slot, : <MySlot.Renderer/>.

Thus, in 20 lines of code, we have implemented a concept that many developers in other technologies really like, and which React lacks.

I published the finished implementation as a react-slotify library on GitHub and as a package on NPM . Already in TypeScript and with support for parameterization of slots . I would be glad to receive constructive criticism.




All Articles