react-grouped-children v0.1.4
react-grouped-children
A React library to allow passing multiple children groups to a component using classic React component inheritance hierarchies instead of attributes.
Table of Contents
Installation
npm install react-grouped-childrenor
yarn add react-grouped-childrenUsage
Defining Children Specification
Create a childrenSpec object where each key is a grouping component name and its value is the component itself.
If you don't have a component for a particular group, you can set its value to null. If value is not set a proxy
component will be generated and its children passed to primary component props, not the proxy component.
const childrenSpec = {
Header: Header,
Footer: null,
} as const;Modifying Component
Wrap your component (main component) with withGroupedChildren and pass childrenSpec as the second argument
to define grouping components.
import { withGroupedChildren } from 'react-grouped-children';
const MyComponentInternal: React.FC<MyComponentProps & GroupedChildrenProps<typeof childrenSpec>> = ({
header,
footer,
...
}) => ...
const MyComponent = withGroupedChildren({ childrenSpec })(MyComponentInternal);With childrenSpec defined as in the example above header prop will contain an array with all instances of Header
component and footer prop will be set to an array of children of the generated proxy component.
⚠ NOTE: be mindful that react may pass children as single child or as an array, so
footermay contain array of arrays. To properly control this you can define traverseChildren function in config or do proper parsing in the component. This project does not address it as it will be fully compatible withReact.ReactNodetype and React can handle it properly if you pass it as is when you rendering the main component.⚠ NOTE: You may have some challenges with
keyproperty if you pass elements to render return "as-is". Most like this will happen only if your customchildrenToArrayfunction does not assign keys properly.
Using Children Groups and HOC
Now, you can pass children group components to your component as follows:
...
return (
<MyComponent>
<MyComponent.Header>
This is header
</MyComponent.Header>
<MyComponent.Footer>
This is footer 1
</MyComponent.Footer>
<MyComponent.Footer>
This is footer 2
</MyComponent.Footer>
</MyComponent>
)
...Configuration
You can configure the behavior of withGroupedChildren by passing an optional config object.
Mode details are in JSDoc comments of Config type (./src/types.ts)
childrenSpec
Specification which defines children components. Should represent object with keys in Pascal case to define
groping component name. Value can be set to an implementation of grouping component or null to use default
component generated by default factory.
childrenToArray
A custom method to convert React component initial children to array on preprocessing stage.
Use when you want to flatten children. The function must always returned a cloned array of children
as it will be mutated. If not defined, React.Children.toArray is used by default.
getComponentName
A custom HOC name generation factory. Returns custom component name.
proxyComponentFactory
A factory which accepts current key of specification object returns a custom implementation of Proxy component.
⚠ Be mindful that this factory should return a new component every for every separate key. This is because the default
componentMatcheris matching types by reference, not by display name or anything else. To change this define your owncomponentMatcher
traverseChildren
A custom method to traverse children from Proxy Component. This method is generic, return type should match with the
second generic (T) parameter of GroupedChildrenProps
componentMatcher
A custom component matcher
Limitations
Static Properties/Methods
To enable grouping functionality withGroupedChildren adds grouping components as static properties to resulting HOC.
You need to ensure that you copy them over if you use other HOC on top. More details here
Why?
in classic React when you want to divide children into groups to use them separately in different
sections of your app you need to pass them as React.ReactNode attributes which often make code reading
challenging, especially when you need to have many attributes like this:
<MyComponent
group1={
<div>
<Head>Some content</Head>
<Body>
Some bigger content
<ul>
{elements.map((e) => <li key={e}>{e}</li>)}
</ul>
</Body>
<div>
}
group2={
<div>
<Footer>
<FooterItem>Item 1</FooterItem>
<FooterItem>Item 2</FooterItem>
<FooterItem>Item 3</FooterItem>
</Footer>
<div>
}
>
{restChildren}
</MyComponent>With this package it becomes more transparent and look more HTML-ish:
<MyComponent>
<MyComponent.Group1>
<div>
<Head>Some content</Head>
<Body>
Some bigger content
<ul>
{elements.map((e) => <li key={e}>{e}</li>)}
</ul>
</Body>
<div>
</MyComponent.Group1>
<MyComponent.Group2>
<div>
<Footer>
<FooterItem>Item 1</FooterItem>
<FooterItem>Item 2</FooterItem>
<FooterItem>Item 3</FooterItem>
</Footer>
<div>
<MyComponent.Group2>
{restChildren}
</MyComponent>or in scenarios like this
<MyList>
<MyList.Item>Item 1<MyList.Item>
<MyList.Item>Item 2<MyList.Item>
<MyList.Item>Item 3<MyList.Item>
<MyList.Item>Item 4<MyList.Item>
<MyList.Item>Item 5<MyList.Item>
<MyList.Heading>Heading Line</MyList.Heading>
<MyList.Footer>Heading Line</MyList.Footer>
</MyList>and access children as classic attributes:
const MyList: React.FC = ({ item, heading, footer }) => {
...
return (<div>
{heading}<br />
<ul>
{item.map((i) => <li>{i}</li>)}
</ul>
{footer}
</div>)
}License
Copyright (c) 2023 Alexandr Yeskov