crtxdmp.

A collection of ideas, snippets and other things.


Vuejs like slots within react

File: slots.tsx
import React from 'react';

function slotHandler(props: React.PropsWithChildren<any>): JSX.Element {
  return <>{props.children}</>;
}

export function initSlot(template?: (props?: any) => React.ReactElement) {
  if (template !== undefined) {
    return template.bind({});
  }

  return slotHandler.bind({});
}

export function filterForSlot(
  nodes: React.ReactElement | React.ReactElement[],
  type: any
): React.ReactNode | React.ReactNode[] {
  if (
    nodes !== null &&
    nodes !== undefined &&
    'type' in nodes &&
    nodes.type === type
  ) {
    return nodes;
  }

  if (nodes !== null && nodes !== undefined && 'find' in nodes) {
    const items = nodes.filter((child: React.ReactNode): boolean => {
      if (
        typeof child !== 'string' &&
        typeof child !== 'number' &&
        typeof child !== 'boolean' &&
        child !== null &&
        child !== undefined &&
        'type' in child
      ) {
        return child.type === type;
      }

      return false;
    });

    return items;
  }

  return false;
}

interface SlotProps {
  type: React.ReactNode;
}

export class Slot extends React.Component<SlotProps, {}> {
  render() {
    const items: React.ReactNode | React.ReactNode[] = filterForSlot(
      // @ts-ignore
      this.props.children,
      this.props.type
    );

    if (
      typeof items !== 'string' &&
      typeof items !== 'number' &&
      typeof items !== 'boolean' &&
      items !== null &&
      items !== undefined
    ) {
      if ('map' in items) {
        return items.map((item: React.ReactNode, index: number) => {
          // eslint-disable-next-line react/no-array-index-key
          return <React.Fragment key={index}>{item}</React.Fragment>;
        });
      }

      return <>{items && 'props' in items ? items.props.children : null}</>;
    }

    return <></>;
  }
}
File: layout.tsx
import { Component } from 'react';
import { initSlot, Slot } from './slots.tsx';

export class Layout extends Component<{}, {}> {
    static Main = initSlot();
    static Side = initSlot();

    render() {
        return (
            <div>
                <div className={"main"}>
                    <Slot
                        type={MainContentContainer.Main}
                        children={this.props.children}
                    />
                </div>
                <div className={"side"}>
                    <Slot
                        type={MainContentContainer.Side}
                        children={this.props.children}
                    />
                </div>
            </div>
        );
    }
}
File: index.tsx
import { Component } from 'react';
import {Layout} from './layout.tsx';

export class MyApp extends Component<{}, {}> {
  static Main = initSlot();
  static Side = initSlot();

  render() {
    return (
        <Layout>
            <Layout.Main>Will render in main slot</Layout.Main>
            <Layout.Side>And this on in the side slot</Layout.Side>
        </Layout>
    )
  }
}

, — Aug 20, 2021