Composes renderless containers and manages them in afterlive. Heavily inspired by react-adopt(context compose), react-powerplug(renderless containers) and redux-restate(fractal state).
The purpose of this library is
- (torque) combine "container"(plugs, context, states), to form more complex structure (gearbox).
- (transmission) provide a way to access them down the tree (train).
- (gear train) provide a way to alter their work (transmission).
That's why - gearbox
import {gearbox} from 'react-gearbox';
// consume any "headless" component
const State = gearbox({
name: <Value initial="Bruce Wayne" />,
nick: <Value initial="Batman" />,
team: <UnstatedContainer />,
enemies: Context.Consumer
});
// create state and access via renderprops
<State render>
{({nick}) => <span>cos I am {nick.value}</span>}
</State>
// create state, and access it later using train
<State>
<span>cos I am <State.train>{({nick}) => nick.value}</State.train></span>
</State>
// unwrap powerplug using transmission
<State>
<State.transmission clutch={({nick}) => ({nick: nick.value})}>
<span>cos I am <State.train>{({nick}) => nick}</State.train></span>
</State.transmission>
</State>
-
gearbox(gears, options?): Gearbox
- creates a Gearbox component. Where -
gears
is a shape of different render-prop-ish components, for example:- ReactElements, or
- FunctionalStatelessComponents, or
- Context.Consumers.
-
options
is an optional field.- options.transmission(input, props) => output - build in transmission, to be applied on gears.
- options.defaultProps - set default props for a future component (note: defaultProps are not reflected on types, PR welcomed)
Produces a Gearbox
- renderless container, which will provide torque from all gears as a render prop.
transmission(gearboxIn, clutch): Gearbox
- created a devired Gearbox, with "clutch" function applied to all stored data. leftMenuController: , topMenuController: , toggler: gear(Toggle, { initial: {} }), // ~Gearbox
is a compound component, and includes 2 sub components- Gearbox.train - React Context based long-range torque provider, which will provide access to the parent Gearbox from the nested components.
- Gearbox.transmission - establish a local (declarative) transmission. Might not be type safe.
Gearbox
has only one prop - render
, if not set - children is a ReactNode. If set - renderProp(function as a children)
gear(component, props)
- a small helper to create elements, without mocking children
prop
Gearboxes
are used to combine gears together, and put them into context.trains
- to access distant gearbox.transmission
- to adapt data structure for the local needs.
Gearbox could merge output from different component using the keys as names. But sometimes you need a bit another structure, for example - just rename fields.
import {gearbox, gear} from 'react-gearbox';
import {Toggle} from 'react-powerplug';
const Gearbox = gearbox({
}, {
transmission: ({leftMenuController, topMenuController}) => ({
isLeftMenuOpen: leftMenuController.value,
isTopMenuOpen: topMenuController.value,
toggleLeftMenuOpen: leftMenuController.toggle,
toggleTopMenuOpen: topMenuController.toggle
})
});
In the same way - you can create a new Components
const Switch = gearbox({
toggle: props => <Toggle {...props} />,
}, {
transmission: ({toggle}) => ({
enabled: toggle.on,
switch: toggle.toggle
}),
defaultProps: {
render: true, // render props as default
local: false, // no context support,
pure: true, // behaves as a pure component (or readux-connect)
}
});
// new component adopted!
<Switch initial={true}>
{({enabled, switch}) => ... }
</Switch>
The same technique could be used to achieve the same results as recompose's withHandlers
While gearbox itself is
withState
.
Gearbox utilizes React.Context observerBits
feature, not calling Trains
if data they consume not changed.
With pure
option enabled this gives you fine control over update propagation flow.
You may opt-out by using Gearbox.directTrain
.
<Gear.train>{({value1}) => <b>will only update, when "value1" got updated</span>}</Gear.train>
<Gear.directTrain>{({value1}) => <b>will update on any GearBox update</span>}</Gear.directTrain>
Gearbox also provides a fancy debugging. Just double check React Dev Tools.
In addition:
- setDebug(boolean | function) - enableds low-level debug.
- Create a gearbox
import {gearbox} from 'react-gearbox';
import {Value, Toggle} from 'react-powerplug';
const Gearbox = gearbox({
// pass a pre-created Element, as you could do with react-adopt
storedValue: <Value initial={42} />,
// pass component, to initialize Element from props (applied only on mount)
toggle: props => <Toggle initial={props.on} />,
// pass React.Context
data: reactContext.Consumer as any, // !! "pure" consumers are not "type safe"
// or pass it as React.Element
context: <reactContext.Consumer children={null}/>,
// you may access all the gearings from above
smartComponent: (props, {data, context} /* all the props from above*/) => <OtherRenderProp />
// Unstated container? Anything "render-prop" capable will work.
stated: <UnstatedContainer />,
});
- Use Gearbox with or without renderprops
By default Gearbox expects ReactNode as a children, and data to be accessed via train
, but you may specify render
prop, to change this behavior to function-as-children.
render
stands for renderProps,on
is required bytoggle
, so required by Gearbox
const App = () => (
<Gearbox on render>
{({storedValue, toggle, data, stated}) => <div>...</div>}
</Gearbox>
)
const App = () => (
<Gearbox on>
<div>...</div>
</Gearbox>
)
- Use Gearbox.train to get the data
// once Gearbox is assembled - you can access it elsewhere using gear trains
const Children = () => (
<Gearbox.train>
{({storedValue, toggle, data, stated}) => <div>...</div>}
</Gearbox>
)
- Use Gearbox.transmission to adopt the data
// component based Transmission are NOT type safe
const Transmission = () => (
<Gearbox.transmission
clutch={({toggle, data}) => ({on:toggle.on, toggle: data.toggle})}
>
<Gearbox.train> {/* you may use <Gearbox.train_untyped> */ }
{({on, toggle}) => <div>...</div>}
</Gearbox.train>
</Gearbox.transmission>
)
const Transmission = () => (
<Gearbox.transmission
clutch={({toggle, data}) => ({on:toggle.on, toggle: data.toggle})}
render /* use as render prop */
>
{({on, toggle}) => <div>...</div>}
</Gearbox.transmission>
)
- Use transmission to achieve type safe transmission.
const TransmittedGear = transmission(Gearbox, ({toggle, data}) => ({on:toggle.on, toggle: data.toggle}));
MIT