🔌 Renderless Pluggable State Containers
I'm working on version 1.0. Follow me on twitter to know when news will come up.
React PowerPlug is a set of pluggable renderless components that provides different types of state and logics so you can use with your dumb components. It creates a state and pass down the logic to the children, so you can handle your data/callbacks.
It's has been created to (but not limited to) use with storybook, react styleguidist, documentations, prototypes. You can also use to create a bunch of uncontrolled components where the outside app don't care about your state, for example a menu dropdown.
import { State, Index, Toggle } from 'react-powerplug'
import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
<State initial={{ offset: 0, limit: 10, totalCount: 200 }}>
{({ state, setState }) => (
<Pagination {...state} onChange={(offset) => setState({ offset })} />
)}
</State>
<Index initial={0}>
{({ index, setIndex }) => (
<Tabs selected={index} onChange={setIndex}>
<Tab title="First tab">Content from the first tab</Tab>
<Tab title="Second tab">Content from the second tab</Tab>
</Tabs>
)}
</Index>
<Toggle initial={true}>
{({ on, toggle }) => (
<Checkbox checked={on} onChange={toggle} />
)}
</Toggle>
// You can also use a `render` prop instead
<Toggle
initial={false}
render={({ on, toggle }) => (
<Checkbox checked={on} onChange={toggle}>
)}
/>
Note: Also known as render props
or children as function
.
For most people, this may look pretty weird. But it's extremely powerful. Usually you render some element whitin your component, but if you are creating an 'agnostic' component or a kind of abstraction component you can not simply render elements, you don't know what the user wants to render. So you render values, actions, states, data... props. You pick these props passing a function to children (or render prop) and now with this data you have total control of what you want to do. Now render what you want to render and do what you want to do. Renderless components are about abstracting some logic without any UI.
React PowerPlug brings to you a bunch of components that act as state and logic containers, so you can get this powerful abstraction and use it any way you want.
Note: with PowerPlug you can use children
or render
prop.
You decide which one is best for you.
Note: This is a kind of a cheat sheet for fast search.
If you want a more detailed API Reference and examples for each component see the Docs
Component Props: { initial }
Render Props: { state, setState }
see docs
<State initial={{ isLoading: false, data: null }}>
{({ state, setState }) => (
<DataReceiver
data={state.data}
onStart={() => setState({ isLoading: true })}
onFinish={data => setState({ data, isLoading: false })}
/>
)}
</State>
Component Props: { initial }
Render Props: { on, off, toggle, setOn }
see docs
<Toggle initial={true}>
{({ on, toggle }) => (
<Checkbox checked={on} onChange={toggle} />
)}
</Toggle>
Component Props: { initial }
Render Props: { isLoading, toggle, setLoading }
see docs
<Loading initial={false}>
{({ isLoading, toggleLoading }) => (
<Button onClick={toggleLoading}>
{isLoading ? 'Loading...' : 'Click me'}
</Button>
)}
</Loading>
Component Props: { initial }
Render Props: { value, setValue }
see docs
<Value initial="React">
{({ value, setValue }) => (
<Select
label="Choose one"
options={['React', 'Preact', 'Vue']}
value={value}
onChange={setValue}
/>
)}
</Value>
Component Props: { initial }
Render Props: { index, setIndex }
see docs
<Index initial={0}>
{({ index, setIndex }) => (
<Tabs selected={index} onChange={setIndex}>
<Tab title="First tab">Content from the first tab</Tab>
<Tab title="Second tab">Content from the second tab</Tab>
</Tabs>
)}
</Index>
Component Props: { initial }
Render Props: { set, get, values }
see docs
<Set initial={{ sounds: true, graphics: 'medium' }}>
{({ set, get }) => (
<Settings>
<ToggleCheck checked={get('sounds')} onChange={c => set('sounds', c)}>
Game Sounds
</ToggleCheck>
<Select
label="Graphics"
options={['low', 'medium', 'high']}
selected={get('graphics')}
onSelect={value => set('graphics', value)}
/>
</Settings>
)}
</Set>
Component Props: { initial }
Render Props: { list, push, pull, sort, setList }
see docs
<List initial={['react', 'babel']}>
{({ list, pull, push }) => (
<div>
<FormInput onSubmit={push} />
{list.map(tag => (
<Tag onRemove={() => pull(value => value === tag)}>
{tag}
</Tag>
)}
</div>
)}
</List>
It's like css pseudo-selectors, but in js :)
Component Props: { }
Render Props: { isHover, bindHover }
see docs
<Hover>
{({ isHover, bindHover }) => (
<div {...bindHover}>
You are {isHover ? 'hovering' : 'not hovering'} this div.
</div>
)}
</Hover>
Component Props: { }
Render Props: { isActive, bindActive }
see docs
<Active>
{({ isActive, bindActive }) => (
<div {...bindActive}>
You are {isActive ? 'clicking' : 'not clicking'} this div.
</div>
)}
</Active>
Component Props: { }
Render Props: { isFocus, bindFocus }
see docs
<Focus>
{({ isFocus, bindFocus }) => (
<div>
<input {...bindFocus} placeholder="Focus me" />
<div>You are {isFocus ? 'focusing' : 'not focusing'} the input.</div>
</div>
)}
</Focus>
Note: v1.0 will have more powerful form-related components, stay tuned!
Component Props: { initial }
Render Props: { value, setValue, bind }
see docs
<Bind initial="hello world">
{({ bind, value }) => (
<div>
<ControlledInput {...bind} />
<div>You typed {value}</div>
</div>
)}
</Bind>
Component Props: { initial }
Render Props: { input }
see docs
<Form initial={{ subject: '', message: '' }}>
{({ input }) => {
const subject = input('subject')
const message = input('message')
return (
<div>
<ControlledInput
placeholder="Subject"
{...subject.bind}
/>
<ControlledTextArea
placeholder="Message"
{...message.bind}
/>
<Submit>Send</Submit>
{/*
input(id) => { bind, setValue, value }
*/}
</div>
)
}
</Form>
If you want to merge two of more components functionalities, you can compose they in a single one.
For complete guide see Compose docs
import { compose } from 'react-powerplug'
const ToggleCounter = compose(Toggle, Counter)
<ToggleCounter>
{({ count, inc, dec, on, toggle }) => (
<ProductCard
{...productInfo}
isFavorited={on}
onFavorite={toggle}
count={count}
onAdd={inc}
onRemove={dec}
/>
)}
</ToggleCounter>
If you need to pass props, especially for initial
, just pass a created element. Internals this will be cloned.
const ToggleCounter = compose(<Toggle initial={false} />, <Counter initial={2} />)
// or just mix it
const ToggleCounter = compose(Toggle, <Counter initial={3} />)
Also, you can use a built-in Compose component and pass components on states
prop
import { Compose } from 'react-powerplug'
<Compose states={[Toggle, Counter]}>
{({ on, toggle, count, inc, dec }) => (
<ProductCard {...} />
)}
</Compose>
Behind the scenes, that's what happens:
<Counter /* passed props */>
{({ count, inc, dec }) => (
<Toggle /* passed props */>
{({ on, toggle }) => (s
<ProductCard
{...productInfo}
isFavorited={on}
onFavorite={toggle}
count={count}
onAdd={inc}
onRemove={dec}
/>
)}
</Toggle>
)}
</Counter>
Because of that, when you use toggle
function, only <Toggle>
will be rerendered, but if you use inc
or dec
functions, both <Counter>
and <Toggle>
will be rerendered. Even using compose()
utility.
Using Toggle in a Checkbox
Using State in a Pagination
yarn add react-powerplug
npm i react-powerplug
<script src="https://unpkg.com/react-powerplug/dist/react-powerplug.min.js"></script>
exposed as ReactPowerPlug
You can help improving this project sending PRs and helping with issues.
Also you can ping me at Twitter