React-React slot fill: Slot & Fill component for merging React subtrees together. Portal on steroids.

react-slot-fill · CircleCI Status Codacy Badge PRs Welcome

Image

Slot & Fill component for merging React subtrees together.

Check out the simple demo on glitch (view source)

Installation

npm install react-slot-fill --save

Check out the examples locally

git clone https://github.com/camwest/react-slot-fill
cd react-slot-fill
npm start

Usage

Note These examples use React Fiber Alpha

Toolbar.js

import { Slot, Fill } from 'react-slot-fill';

const Toolbar = (props) =>
  <div>
    <Slot name="Toolbar.Item" />
  </div>

export default Toolbar;

Toolbar.Item = (props) =>
  <Fill name="Toolbar.Item">
    <button>{ props.label }</button>
  </Fill>

Feature.js

import Toolbar from './Toolbar';

const Feature = () =>
  [
    <Toolbar.Item label="My Feature!" />
  ];

App.js

import Toolbar from './Toolbar';
import Feature from './Feature';

import { Provider } from 'react-slot-fill';

const App = () =>
  <Provider>
    <Toolbar />
    <Feature />
  </Provider>

ReactDOMFiber.render(
  <App />,
  document.getElementById('root')
);

Components

Creates a Slot/Fill context. All Slot/Fill components must be descendants of Provider. You may only pass a single descendant to Provider.

interface Provider {
  /**
   * Returns instances of Fill react components
   */
  getFillsByName(name: string): Fill[];
  /**
   * Return React elements that were inside Fills
   */
  getChildrenByName(name: string): React.ReactChild[];
}

getFillsByName and getChildrenByName are really useful for testing Fill components. See src/lib/__tests/Provider.test.tsx for an example.

Expose a global extension point

import { Slot } from 'react-slot-fill';

Props

interface Props {
  /**
   * The name of the component. Use a symbol if you want to be 100% sue the Slot
   * will only be filled by a component you create
   */
  name: string | Symbol;

  /**
   * Props to be applied to the child Element of every fill which has the same name.
   *
   *  If the value is a function, it must have the following signature:
   *    (target: Fill, fills: Fill[]) => void;
   *
   *  This allows you to access props on the fill which invoked the function
   *  by using target.props.something()
   */
  fillChildProps?: {[key: string]: any}

  /**
   * A an optional function which gets all of the current fills for this slot
   * Allows sorting, or filtering before rendering. An example use-case could
   * be to only show a limited amount of fills.
   *
   * By default Slot injects an unstyled `<div>` element. If you want greater
   * control over presentation use this function.
   *
   * @example
   * <Slot name="My.Slot">
   * {(items) => <Component>{items}</Component>}
   * </Slot>
   */
  children?: (fills) => JSX.Element
}

Render children into a Slot

import { Fill } from 'react-slot-fill';

Props

interface Props {
  /**
   * The name of the slot that this fill should be related to.
   */
  name: string | Symbol

  /**
   * one or more JSX.Elements which will be rendered
   */
  children: JSX.Element | JSX.Element[]
}

You can add additional props to the Fill which can be accessed in the parent node of the slot via fillChildProps.

Comments

  • fillChildProps cannot pass functions through to fill
    fillChildProps cannot pass functions through to fill

    Oct 12, 2017

    Passing functions through to fills is currently not possible. Instead they are called during the transform step:

    const value = fillChildProps[key];
    
    if (typeof value === 'function') {
        acc[key] = () => value(fill, this.fills);
    } else {
        acc[key] = value;
    }
    

    This prevents us from passing callbacks like onChange into fills.

    Reading the readme, this seems to be by design:

      /**
       * Props to be applied to the child Element of every fill which has the same name.
       *
       *  If the value is a function, it must have the following signature:
       *    (target: Fill, fills: Fill[]) => void;
       *
       *  This allows you to access props on the fill which invoked the function
       *  by using target.props.something()
       */
      fillChildProps?: {[key: string]: any}
    

    Could you please help me understand why this is the case? If the main use case is to get a 'ref' to the fill, wouldn't it be nicer and less limiting to make that a separate property?

    Reply
  • Fills ordered by least recently mounted
    Fills ordered by least recently mounted

    Jun 13, 2018

    Issue:

    Fills are rendered in reverse order that they are mounted

    Expected:

    Fills are rendered in their order in the react tree

    Example:

    https://conscious-violet.glitch.me/ See that the order that you toggle survey/news affects their button order in the toolbar

    I'm happy to open a PR if you can think of a way to calculate the order. My only thought was to base it on a timestamp/global counter since I don't think components have access to their siblings but that seems like a poor solution.

    Reply
  • NPM package.json contains an invalid peer dependency
    NPM package.json contains an invalid peer dependency

    Jun 21, 2018

    Looking at the package.json in this repo, it contains the following peer dependency

    "prop-types": "^15.5.8",
    

    However, when using the 2.0.1 package from NPM I get this in the package.json

    "prop-types": "^16.0.0",
    

    I also see the same when I view the package.json through unpkg (in case I had some weird build step ruining all of my packages) here.

    This doesn't have any run time impact as far as I can tell but does put a couple warnings into my console about having invalid peer dependencies. Would be nice to clean these up and I was going to submit a PR to fix it but it appears to only be an issue in the published package.

    Reply
  • Maintenance
    Maintenance

    Dec 10, 2018

    Hi there!

    This repository is currently outdated. My suggestion is to split the demo package from the lib itself so they can be updated at a different pace.

    Also, now there's the opportunity to play with the new Hooks API, I would like this library to go forward.

    As part of the Contributing guide, I'm an Intent to implement.

    Reply
  • avoid own key rewriting
    avoid own key rewriting

    Feb 20, 2020

                                                                                                                                                                                                           
    Reply
  • Update deprecated react lifecycles
    Update deprecated react lifecycles

    Dec 1, 2021

    Need to be updated according to this: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html

    Reply
  • Put the repo url instead of the demo url in the npm publication page
    Put the repo url instead of the demo url in the npm publication page

    Mar 15, 2017

    Hi,

    I really liked your presentation during react conf! But I have a hard time to find back your github. The npm package lists the demo instead of the repo, which I think is not the usual practice. Otherwise a big thank you for this lib!

    Cheers,

    Reply
  • Compare Slot/Fill to React 16 Portals?
    Compare Slot/Fill to React 16 Portals?

    Nov 15, 2017

    Can you provide a brief description of pro/cons for using this repo vs using Portals in React 16?

    Very cool concepts here btw, dynamic component extensibility would certainly be useful imo.

    Thanks.

    Reply
  • Use `{}` instead of `void` for component state to avoid build errors.
    Use `{}` instead of `void` for component state to avoid build errors.

    Aug 6, 2017

    Updates to TypeScript/the React type definitions have caused issues for me when a component uses void instead of {} to indicate that a component doesn't make use of state. With default generics in [email protected] and the subsequent update over at DefinitelyTyped you can just omit the void so your class declaration looks like:

    class MyComponent extends React<MyComponentProps> {
    }
    

    And things will work just fine. However so the type definitions in this repo are more backwards compatible I opte to substitute {} in for void so pre 2.3 consumers don't have any issues.

    Here's the build error I get when a component uses void:

    [at-loader] Checking finished with 2 errors
    [at-loader] ./src/common/components/Modal/index.tsx:107:13
        TS2605: JSX element type 'Fill' is not a constructor function for JSX elements.
      Types of property 'setState' are incompatible.
        Type '{ <K extends never>(f: (prevState: void, props: Props) => Pick<void, K>, callback?: () => any): v...' is not assignable to type '{ <K extends never>(f: (prevState: {}, props: any) => Pic
    k<{}, K>, callback?: () => any): void; <...'.
          Types of parameters 'f' and 'f' are incompatible.
            Types of parameters 'prevState' and 'prevState' are incompatible.
              Type 'void' is not assignable to type '{}'.
    
    [at-loader] ./src/common/components/Modal/index.tsx:139:13
        TS2605: JSX element type 'Fill' is not a constructor function for JSX elements.
    
    Reply
  • Getting
    Getting "ReferenceError: mitt is not defined" in v1.0.0-alpha.11

    May 10, 2017

    Seems like the "module" target bundle doesn't import mitt, which results in the error "ReferenceError: mitt is not defined". Is this a bug or am I missing something?

    I set up a simple example project and followed the usage notes. I use Webpack 2 for bundling. Please let me know if you need any additional info!

    Thanks!

    Reply
  • 1.0.0 Roadmap
    1.0.0 Roadmap

    Mar 16, 2017

    Themes

    • replace the prototype we have in production at Autodesk with this library
    • [x] write proper test suite & setup guide
    • [x] figure out how to deal with React Fiber situation - solution use [email protected] dependency

    Engineering

    • [x] Write unit tests for components
    • [x] Extract & refactor lib into separate modules
    • [x] Decide if view registry should be truly global or if it should be instanced with a provider component and passed via context
    • [x] Convert to typescript #10
    • [ ] Add better error message if Provider is missing

    Infra

    • Come up with a plan to verify we've got good browser support
    • Set up circle ci
      • [x] Run unit tests
      • [x] Deploy github pages automatically on green build
      • [ ] Deploy canary release automatically on green build
    • [x] Set up code coverage measurement with codacy
    • [ ] Export pkg.module

    Documentation

    • [ ] Write some sample applications in addition to the demos we've built so far.
    • [x] Add a forkable example on https://glitch.com/ that's really easy to play with
    • [ ] Show an example using Redux

    Bugs

    Tech Debt

    Development Experience

    • This is looking pretty good since we are just literally using the default CRA
    roadmap 
    Reply
  • "ReferenceError: mitt is not defined" after upgrading to 1.0.2

    Oct 16, 2017

    After updating to 1.0.2 I receive import errors "ReferenceError: mitt is not defined"

    image

    Does this has to do with this commit? https://github.com/camwest/react-slot-fill/commit/f4087870aa3f74b86cb55f882e9f398586800256

    Reply