Introducing CSS cascading to React components with first-class Typescript support right out of the box.
DRY out your code. Repeat yourself less with less hassle.
npm | yarn |
---|---|
npm install react-cascade-component |
yarn add react-cascade-component |
import Cascade from 'react-cascade-component';
const App = () => {
const onClickHandler: React.MouseEventHandler<HTMLButtonElement> = (e) => {
alert(`Button ${e.currentTarget.id} was clicked!`);
};
return (
<Cascade cascadeTo="button" cascadeProps={{ onClick: onClickHandler }}>
<div />
<div />
<button id="1" />
<button id="2" />
<button id="3" />
</Cascade>
);
};
Cascade by default is a div
element but can be of any JSX.IntrinsicElement
by setting
the as
prop value or using Cascade.[JSX.IntrinsicElement]
.
<Cascade as="span">{/* ... */}</Cascade>
<Cascade.span>{/* ... */}</Cascade.span>
prop | type | examples |
---|---|---|
Specifies what <Cascade/> is rendered as. Defaults to div
|
undefined keyof JSX.IntrinsicElement React.JSXElementConstructor<any>
|
"div" "span" MyCustomComponent |
Specifies which child elements cascadeProps is sent to. If null
will send cascadeProps to all children elements. If undefined will
default to the outer cascadeTo if inside another <Cascade>
component. If not, it will send to all children elements.
|
undefined null keyof JSX.IntrinsicElement React.JSXElementConstructor<any>
(keyof JSX.IntrinsicElement | React.JSXElementConstructor<any>)[] |
"div" ["span", "div"] MyCustomComponent ['span', MyCustomComponent] |
The `props` to cascade to child elements |
any 1
|
{"className": "foobar", "customKey": "customValue"}
|
Whether or not the <Cascade> will absorb the
properties itself, or simply pass it on. Defaults to true
|
boolean
|
true false
|
Any other properties to be used by <Cascade>
|
JSX.IntrinsicElement[typeof as]
|
Valid other properties may be ref className id and more
|
1 - The is not technically true, but practically true. It has a type dependent on cascadeTo
, and will have type inference if cascadeTo
is provided
The <Cascade>
component has a callback parameter on cascadeTo
which means you can specify handling, by default the callback is (callbackProps, originalProps) => {...callbackProps, ...originalProps}
, ie. a shallow merge.
<Cascade
cascadeTo={[
['button', (c, o) => ({ ...c.buttonProps, ...o })],
[MyCustomComponent, (c, o) => ({ ...c.customProps, ...o })],
]}
cascadeProps={{
buttonProps: {
onClick: onClickHandler,
},
customProps: {
className: 'foobar'
}
}}
>
<button />
<div />
<MyCustomComponent />
</Cascade>
You can also specify a function instead:
<Cascade
cascadeTo={(t, c, o) => {
if (t === 'button') {
return {...c.buttonProps, ...o}
}
if (t === MyCustomComponent) {
return {...c.customProps, ...o}
}
}}
cascadeProps={{
buttonProps: {
onClick: onClickHandler,
},
customProps: {
className: 'foobar'
}
}}
>
<button />
<div />
<MyCustomComponent />
</Cascade>
The <Cascade>
component can pass through to each other. By default it will both absorb and pass properties.
Nested <Cascade>
components will cascadeTo the same constrained types.
<Cascade absorbProps={false} />
will disable absorbing props but will still pass through through properties.
<Cascade className="foo" cascadeProps={{ className: 'bar' }}>
<Cascade
className="bang"
cascadeTo={[Cascade, 'span']}
>
<Cascade.span> {/* Cascade.span !== 'span' */}
<div />
<div />
<span />
<label />
</Cascade.span>
<Cascade
cascadeTo={null}
>
<span /> {/* className="bar" */}
<label /> {/* className="bar" */}
</Cascade>
<Cascade> {/* className="bar" */}
<span /> {/* className="bar" */}
<label />
</Cascade>
<Cascade /* className=undefined */
absorbProps={false}
cascadeTo="label"
>
<span />
<label /> {/* className="bar" */}
</Cascade>
<div>
<span />
<label />
</div>
</Cascade>
</Cascade>;
If you are simply using react-cascade-component
as a means to transfer props deeply in your component,
instead consider using React's built-in useContext
.
Contributions are welcome!
Licensed under the MIT License, Copyright © 2023-present Cody Duong.
See LICENSE for more information.