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 | undefinedkeyof JSX.IntrinsicElementReact.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. | undefinednullkeyof JSX.IntrinsicElementReact.JSXElementConstructor<any> (keyof JSX.IntrinsicElement | React.JSXElementConstructor<any>)[] | "div"["span", "div"]MyCustomComponent['span', MyCustomComponent] |
| The `props` to cascade to child elements | any1 | {"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.