Skip to content

hooriza/react-use-slot

Repository files navigation

react-use-slot

Inspired by https://vuejs.org/guide/components/slots.html

Installation

$ npm install react-use-slot

Slot Content and Outlet

<FancyButton>
  Click me! <!-- slot content -->
</FancyButton>
const FancyButton = ({ children }) => {
  const Slot = useSlot(children);
  return (
    <button className="fancy-btn">
      <Slot />
    </button>
  );
}

The final rendered DOM

<button class="fancy-btn">
  Click me!
</button>

Frankly, you don't need to use react-use-slot for this purpose. You can write FancyButton component just like below.

const FancyButton = ({ children }) => {
  return (
    <button className="fancy-btn">
      {children}
    </button>
  );
}

Well.. How about next case?

Fallback Content

You can place the fallback content inside Slot tag

const SubmitButton = ({ children }) => {
  const Slot = useSlot(children);
  return (
    <button type="submit">
      <Slot>Submit</Slot>
    </button>
  );
}

When you use SubmitButton that has no content

<SubmitButton />

This will render the fallback content, "Submit".

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

Or use it with content

<SubmitButton>Save</SubmitButton>

it's rendered DOM

<button type="submit">Save</button>

Hmm... You don't need to use this package for this purpose either. You can write just like below. React is powerful enough.

const FancyButton = ({ children }) => {
  return (
    <button className="fancy-btn">
      {children ?? 'Save'}
    </button>
  );
}

Last. What about the following cases?

Named Slots

Finally, there are why you need react-use-slot from here.

<MyComponent>
  <div slot="header">HEADER</div>
  <p slot="footer">FOOTER</p>
  CONTENT
</MyComponent>
const MyComponent = ({ children }) => {
  const Slot = useSlot(children);

  return (
    <div className="my-component">
      <header>
        <Slot name="header" />
      </header>
      <main className="content">
        <Slot />
      </main>
      <footer>
        <Slot name="footer" />
      </footer>
    </div>
  );
};

This will render below DOM

<div class="my-component">
  <header>
    <div>HEADER</div>
  </header>
  <main class="content">
    CONTENT
  </main>
  <footer>
    <p>FOOTER</p>
  </footer>
</div>

Scoped Slots

There are cases when the parent scope needs data provided from the child.

In this case, how to show the data with slot?

const List = ({ children }) => {
  const Slot = useSlot(children);
  const dataList = ['foo', 'bar', 'baz'];

  return (
    <>
      <header><Slot name="header" /></header>
      <ul>
        {dataList.map((data) => <Slot name="item" />)}
      </ul>
    </>
  )
}
<List>
  <h1 slot="header">Title</h1>
  <li slot="item">How to show data??</li>
</List>

You can do it like below.

const List = ({ children }) => {
  const Slot = useSlot(children);
  const dataList = ['foo', 'bar', 'baz'];

  return (
    <>
      <header><Slot name="header" /></header>
      <ul>
        {dataList.map((data) => (
          <Slot key={data} name="item" data={data} />)
        )}
      </ul>
    </>
  )
}
<List>
  <h1 slot="header">Title</h1>
  <li slot="item">{(data) => data}</li>
</List>

This will render below DOM

<header><h1>Title</h1></header>
<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
</ul>

To avoid react warning with React.Fragment

Using the slot attribute with React.Fragment will cause a warning.

import React from 'react';

// Warning: Invalid prop `slot` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.
<React.Fragment slot="foo">Foo</React.Fragment>

This warning doesn't affect the behavior, but if you want the warning not to be raised, import the Fragment from react-use-slot and use it.

import { Fragment } from 'react-use-slot';

<Fragment slot="foo">Foo</Fragment>