Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-component-catalog

natterstefan144Apache-2.02.1.1TypeScript support: included

Manage a catalog (registry) of react-components easily.

react-components, react-registry, react-catalog, registry, catalog, components, react

readme

React-Component-Catalog

npm version Build Status Coverage Status GitHub license

Dependencies Known Vulnerabilities Commitizen friendly lerna

React-Component-Catalog is a library for individually registering, retrieving, and rendering React components based on your own conditions (eg. different component for various clients, sites, ...).

Getting started

npm i react-component-catalog --save

# or
yarn add react-component-catalog

Then install the correct versions of each peerDependency package, which are listed by the command:

npm info "react-component-catalog@latest" peerDependencies

If using npm 5+, use this shortcut:

npx install-peerdeps --dev react-component-catalog

# or
yarn add react-component-catalog -D --peer

Upgrade from 1.x.x to 2.0.0

Catalog Data Structure changes

When upgrading to 2.0.0, one needs to change the Catalog's data structure.

// catalog.js
- import { Catalog } from 'react-component-catalog'
import Button from './button'

-const catalog = new Catalog({
-  components: {
-    Button,
-  },
-})
+const catalog = {
+  Button,
+)

export default catalog

CatalogProvider changes

Previously, CatalogProvider rendered it's children with an empty catalog, when none was provided. In 2.x it renders null instead. Same happens, when no child component is provided.

import { CatalogProvider } from 'react-component-catalog'
import catalog from './catalog' // your apps catalog

const App = () => (
- <CatalogProvider catalog={new Catalog({ components: catalog })}>
+ <CatalogProvider catalog={catalog}>
    <div>Hello</div>
  </CatalogProvider>
)

CatalogProvider accepts an object and no instance of Catalog anymore.

useCatalog and catalog changes

getComponent does not return null anymore when a component is not found, instead it returns undefined.

import React from 'react'
import CatalogComponent, { useCatalog } from 'react-component-catalog'

const App = () => {
- const { catalog } = useCatalog()
+ const catalog = useCatalog()

- console.log('available components', catalog._components)
+ console.log('available components', catalog._catalog)

  const Button = catalog.getComponent('Button')

  // ...
}

Catalog changes

Catalog is not exported anymore, so code like does not work anymore:

- import { Catalog } from 'react-catalog-component'

CatalogComponent and Module Augmentation

The CatalogComponents interface can be augmented to add more typing support.

// react-component-catalog.d.ts
declare module 'react-component-catalog' {
  export interface CatalogComponents {
    Title: React.FunctionComponent<{}>
  }
}

Whenever you use the CatalogComponent now you can do the following to get full typing support (opt-in feature). When you do not provide the interface, any string, string[] or Record<string, any> value for component is allowed.

const App = () => (
  <CatalogComponent<CatalogComponents> component="Title">
    Hello World
  </CatalogComponent>
)

// this works too, but `component` has no typing support
const App = () => (
  <CatalogComponent component="Title">Hello Base</CatalogComponent>
)

Attention: it is recommended to use CatalogComponents only when it was augmented. Because it represents an empty interface and without adding your own custom properties it will match everything.

Basic Usage

Create a Catalog

// button.js
import React from 'react'

const Button = props => <button>{props.children}</button>

export default Button
// catalog.js
import Button from './button'

const catalog = {
  Button,
}

export default catalog

Create a nested Catalog

It is also possible to add a nested components-object to the Catalog. This allows registering variations of a component. Take an article for instance. You might want to register different types of the component. There might be a AudioArticle, VideoArticle and a BaseArticle component you want to use. You can add them to the catalog like this:

// catalog.js
// different types of articles
import AudioArticle from './audio-article'
import BaseArticle from './base-article'
import VideoArticle from './video-article'

const catalog = {
  ArticlePage: {
    AudioArticle,
    BaseArticle,
    VideoArticle,
  },
}

export default catalog

And you could later use it like this:

// app.js
import React from 'react'
import CatalogComponent, { useCatalog } from 'react-component-catalog'

const App = props => {
  const { isAudioArticle, isVideoArticle } = props
  const catalog = useCatalog()

  // get the ArticlePage object from the catalog
  const ArticlePage = catalog.getComponent('ArticlePage')

  // or get them one by one with one of the following methods
  // const BaseArticle = catalog.getComponent('ArticlePage.BaseArticle')
  // <CatalogComponent component="ArticlePage.BaseArticle" />

  if (isAudioArticle) {
    return <ArticlePage.AudioArticle {...props} />
  }

  if (isVideoArticle) {
    return <ArticlePage.VideoArticle {...props} />
  }

  return <ArticlePage.BaseArticle {...props} />
}

export default App

Create a CatalogProvider

// index.js
import React from 'react'
import ReactDOM from 'react-dom'

import { CatalogProvider } from 'react-component-catalog'
import catalog from './catalog'
import App from './app'

ReactDOM.render(
  <CatalogProvider catalog={catalog}>
    <App />
  </CatalogProvider>,
  document.getElementById('_root'),
)

Nesting CatalogProvider

<CatalogProvider /> can be nested, whereas the inner provider will extend and overwrite the parent provider.

// setup catalogs
const catalog = {
  OuterComponent: () => <div>OuterComponent</div>,
  Title: ({ children }) => <h1>OuterTitle - {children}</h1>,
}

const innerCatalog = {
  InnerComponent: () => <div>InnerComponent</div>,
  Title: ({ children }) => <h2>InnerTitle - {children}</h2>, // inner CatalogProvider overwrites Title of the outer catalog
}

// usage
const App = () => (
  <CatalogProvider catalog={catalog}>
    <CatalogProvider catalog={innerCatalog}>
      <Content />
    </CatalogProvider>
  </CatalogProvider>
)

<Content /> can access components inside the catalog and innerCatalog. If the innerCatalog contains a component with the same name than in the catalog it will overwrite it. In this case <Title /> gets overwritten in the inner provider.

Import and use the catalog (with react-hooks)

// app.js
import React from 'react'
// useCatalog is a react-hook
import CatalogComponent, { useCatalog } from 'react-component-catalog'

const App = () => {
  const catalog = useCatalog()
  const Button = catalog.getComponent('Button')

  // you can also first check if it exists
  const hasButton = catalog.hasComponent('Button')

  // or you use them with the <CatalogComponent /> component
  return (
    <div>
      <CatalogComponent component="Title">Hello Client1</CatalogComponent>
      <CatalogComponent
        component="Card"
        {/* the fallbackComponent can either be a new component, or a component
          from the catalog */}
        fallbackComponent={() => <div>Component not found</div>}
        { /* fallbackComponent="FallbackComponent" */ }
      >
        Hello Card
      </CatalogComponent>
      {Button && <Button />}
    </div>
  )
}

export default App

Use catalog with ref

Refs provide a way to access DOM nodes or React elements created in the render method. (Source: reactjs.org)

It is possible to use react-component-catalog with ref as well. It would look similar to (works also with <CatalogComponent />):

const TestComponent = withCatalog(props => (
  <button {...props} type="button">
    Hello Button
  </button>
))

/* eslint-disable react/no-multi-comp */
class App extends React.Component {
  constructor(props) {
    super(props)
    this.setRef = React.createRef()
  }

  render() {
    // or <CatalogComponent component="TestComponent" ref={this.setRef} />
    return (
      <CatalogProvider catalog={{ TestComponent }}>
        <TestComponent ref={this.setRef} />
      </CatalogProvider>
    )
  }
}

How to build and test this package

# -- build the package --
yarn
yarn build
# -- test the package in an example app --
# run the example in watch-mode
yarn watch

# or run the example in production mode
cd packages/example
yarn build
yarn start

How to release and publish the package

This package uses standard-version and commitizen for standardizing commit messages, release tags and the changelog.

When you're ready to release, execute the following commands in the given order:

  1. git checkout master
  2. git pull origin master
  3. yarn release:prepare: select the proper version
  4. yarn release --release-as <version>: use the version selected before (e.g. beta releases: yarn release --prerelease beta --release-as major)
  5. git push --tags
  6. cd packages/react-component-catalog && yarn publish: do not select a new version.

TODO: automate and optimize scripts, see 3ba95ec and 2eb2a8b

Credits

Inspired by Building a Component Registry in React by Erasmo Marín. I did not find a package implementing his thoughts and ideas in such a straightforward way. That is why, I decided to create it.

Licence

Apache 2.0

Maintainers


Stefan Natter

changelog

React-Component-Catalog Changelog

All notable changes to this project will be documented here. The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

2.1.1 (2021-03-29)

Fixes

  • added missing esm/index.d.ts declaration file (#70) (56e06cc), closes #68

2.1.0 (2020-10-27)

Features

  • updated bundling & ts (module augmentation); lerna monorepo (#57) (4dcb5c4)

2.0.0 (2020-07-22)

⚠ BREAKING CHANGES

  • flat catalog (no extra components prop anymore) (#49) (ae69468)

Features

  • unified handling of undefined catalog in all components (#53) (26bed89)
  • ts: properly type getComponent and hasComponent of ICatalog (#52) (4584408)

2.0.0-beta.0 (2019-12-15)

Features

1.3.0 (2019-11-18)

Features

  • fallbackComponent can also be selected from catalog now (#48) (6be75b1)

1.2.0 (2019-07-21)

Features

  • disable/remove renovate (for now) (319725e)
  • logging: use babel-plugin-dev-expression for logging errors (#41) (6fdd918)

1.1.1 (2019-05-15)

Bug Fixes

  • utils: return null when flattenObjectKeys receives invalid obj (06081e5)

1.1.0 (2019-05-14)

Bug Fixes

  • CatalogComponent: Fix missing displayName (fa3e14f)

Features

  • catalog: Add new catalog.hasComponent function (c4c10a2)
  • catalog: Allow nested components object in catalog (ca236ad)
  • CatalogComponent: CatalogComponent supports nested components (369375b)

1.0.1 (2019/04/29)

Added

  • esm support (build esm module and add id as module in package.json)

2019/04/06 1.0.0

Added

2019/03/28 0.6.1

Changed

Fixed

  • fixed build script and main field issue, causing usage issues

2019/03/26 0.6.0

Added

  • catalog can be prefixed to prevent overwriting during nesting providers
  • added "sideEffects": false to package.json
  • added "module": "lib/index.js" to package.json (including lib output)

2019/03/13 0.5.0

Added

  • Dev: eslint support
  • CatalogProvider can be nested and they consume their parent's catalog
  • react-hooks support with new useCatalog. It is recommended to not use withCatalog anymore but useCatalog whenever possible.

Changed

As this package depends on react-hooks, "react": "^16.8.0" and "react-dom": "^16.8.0" are required (see peerDependencies in package.json) from now on.

2019/03/11 0.4.0

Added

  • fallbackComponent (node) property can be used to render alternative component when the requested component does not exist
  • log error when <CatalogComponent /> is used without a <CatalogProvider /> context

2019/02/02 0.3.0

Changed

  • moved from old react Context API to latest Context API

Removed

  • prop-types dependency

2019/02/01 0.2.0

Added

  • jest test setup

Changed

  • renamed name property to component
    • eg. <CatalogComponent name="Button"> is now <CatalogComponent component="Button">

2019/01/31 0.1.0

Added

  • Initial project setup with example app