Skip to content

Commit

Permalink
feat(CatalogComponent): CatalogComponent supports nested components
Browse files Browse the repository at this point in the history
  • Loading branch information
natterstefan committed May 13, 2019
1 parent 7a3ec00 commit 369375b
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 21 deletions.
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ yarn add react-component-catalog -D --peer

## Basic Usage

### First register the components
### Create a Catalog

```jsx
// button.js
Expand All @@ -66,6 +66,68 @@ const catalog = new Catalog({
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:

```jsx
// catalog.js
import { Catalog } from 'react-component-catalog'

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

const catalog = new Catalog({
components: {
ArticlePage: {
AudioArticle,
BaseArticle,
VideoArticle,
},
},
})

export default catalog
```

And you could later use it like this:

```jsx
// 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

```jsx
Expand Down
8 changes: 5 additions & 3 deletions src/components/catalog-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
/* eslint-disable no-console */
import React from 'react'

import { flattenObjectKeys } from '../utils'

import useCatalog from './use-catalog'

/**
Expand Down Expand Up @@ -40,7 +42,7 @@ const CatalogComponent = React.forwardRef((props, ref) => {
return null
}

const Component = catalog._components[component]
const Component = catalog.getComponent(component)
if (Component) {
return <Component {...others} ref={ref} />
}
Expand All @@ -51,8 +53,8 @@ const CatalogComponent = React.forwardRef((props, ref) => {

// if no component was found, tell the developer and fail gracefully
console.warn(
`No component for "${component}" was found in the component catalog. The catalog contains the following components:`,
catalog._components && Object.keys(catalog._components),
`"${component}" not found in component catalog. The catalog contains only:`,
catalog._components && flattenObjectKeys(catalog._components),
)

return null
Expand Down
32 changes: 29 additions & 3 deletions src/components/catalog-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CatalogProvider from './catalog-provider'
import { withCatalog } from './with-catalog'

const TestComponent = () => <div>Hello World</div>
const BaseArticle = () => <div>Hello BaseArticle</div>

const FallbackComponent = () => <div>Fallback</div>

Expand All @@ -21,6 +22,15 @@ describe('CatalogComponent', () => {
testCatalog = new Catalog({
components: {
TestComponent,
ArticlePage: {
BaseArticle,
},
Pages: {
NestedPage: () => null,
AnotherNestedPage: {
OtherPage: () => null,
},
},
},
})

Expand Down Expand Up @@ -53,6 +63,16 @@ describe('CatalogComponent', () => {
expect(wrapper.find(TestComponent).text()).toStrictEqual('Hello World')
})

it('renders a requested nested component fully functional', () => {
const wrapper = mount(
<CatalogProvider catalog={testCatalog}>
<CatalogComponent component="ArticlePage.BaseArticle" />
</CatalogProvider>,
)

expect(wrapper.find(BaseArticle).text()).toStrictEqual('Hello BaseArticle')
})

it('renders a requested component with additional props', () => {
const wrapper = mount(
<CatalogProvider catalog={testCatalog}>
Expand Down Expand Up @@ -151,8 +171,14 @@ describe('CatalogComponent', () => {
// test if the developer was notified
expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.warn).toHaveBeenLastCalledWith(
'No component for "NotAvailableComponent" was found in the component catalog. The catalog contains the following components:',
['TestComponent'],
'"NotAvailableComponent" not found in component catalog. The catalog contains only:',
[
'TestComponent',
// must also work with nested components
'ArticlePage.BaseArticle',
'Pages.NestedPage',
'Pages.AnotherNestedPage.OtherPage',
],
)
})

Expand All @@ -166,7 +192,7 @@ describe('CatalogComponent', () => {
// test if the developer was notified
expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.warn).toHaveBeenLastCalledWith(
'No component for "NotAvailableComponent" was found in the component catalog. The catalog contains the following components:',
'"NotAvailableComponent" not found in component catalog. The catalog contains only:',
[],
)
})
Expand Down
36 changes: 36 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,39 @@ export const get = (obj, path, def) => {

return current
}

/**
* Returns an array with the flattend Object.keys of a given object
*
* Inspired by flattenObject published on https://30secondsofcode.org/object
*
* ```js
* const obj = {
* a: 1,
* b: 2,
* c: {
* d: 3,
* e: {
* f: 4,
* },
* },
* }
* ```
*
* would result in `['a', 'b', 'c.d', 'c.e.f']`
*/
export const flattenObjectKeys = (obj, prefix = '', flatObjectProps = []) => {
Object.keys(obj).reduce((acc, k) => {
const pre = prefix.length ? `${prefix}.` : ''

if (typeof obj[k] === 'object') {
Object.assign(acc, flattenObjectKeys(obj[k], pre + k, flatObjectProps))
} else {
flatObjectProps.push(pre + k)
}

return acc
}, {})

return flatObjectProps
}
38 changes: 24 additions & 14 deletions src/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { get } from './utils'
import { get, flattenObjectKeys } from './utils'

describe('utils', () => {
describe('get', () => {
const obj = {
a: 1,
b: 2,
c: {
d: 3,
e: {
f: 4,
},
const obj = {
a: 1,
b: 2,
c: {
d: 3,
e: {
f: 4,
},
}
},
}

const objArray = {
a: [{ b: 1 }, { b: 2 }],
}
const objArray = {
a: [{ b: 1 }, { b: 2 }],
}

describe('get', () => {
it('returns undefined when path (nested or not) is not found', () => {
expect(get(obj, 'ab')).toBeUndefined()
expect(get(objArray, 'a[2].b')).toBeUndefined()
Expand All @@ -43,4 +43,14 @@ describe('utils', () => {
expect(get(objArray, 'a[0].b')).toStrictEqual(1)
})
})

describe('flattenObjectKeys', () => {
it("returns an array containing the object's properties", () => {
expect(flattenObjectKeys(obj)).toStrictEqual(['a', 'b', 'c.d', 'c.e.f'])
})

it("returns an array containing the object's (with array) properties", () => {
expect(flattenObjectKeys(objArray)).toStrictEqual(['a.0.b', 'a.1.b'])
})
})
})

0 comments on commit 369375b

Please sign in to comment.