Type aliases
Const AsyncController
Async
Controller: React.ComponentType<ControllerProps<AsyncSlice>> = Controller as AsyncController
ControllableChildren
ControllableChildren: function
Type declaration
-
- (state: State, dispatch: function): React.ReactNode
-
Parameters
-
state: State
-
dispatch: function
-
-
Type parameters
Parameters
Returns void
Returns React.ReactNode
SubscriberChildren
SubscriberChildren: function
Type declaration
-
- (state: S, next: function): React.ReactNode
-
Parameters
Returns React.ReactNode
SubscriberDecorator
SubscriberDecorator: function
Type declaration
-
- <D>(deriveProps: function): function
-
Type parameters
Parameters
-
deriveProps: function
-
- (state: S, next: function): D
Returns function
-
- <P>(Component: React.ComponentType<P extends D ? P : D>): ComponentClass<Subtract<P, D>>
-
Type parameters
Parameters
-
Component: React.ComponentType<P extends D ? P : D>
Returns ComponentClass<Subtract<P, D>>
stately-react
[api] [github]

This module contains type-safe components for simplifying React state management.
Usage Guides
Subscribable
Components: Components that create type-safe subscriptions (e.g. to a Redux store)<Subscription>
<Subscription>
with<Subscriber>
sThe subscriber()
decoratorAsync
Components: Components that perform type-safe asynchronous operations, manage their state, and provide status updates (active, completed, error)<Async>
operations<Async>
operations<Async>
operations with ReduxControllable
Components: Components that use a reducer to manage internal state, or can defer their state to a controlling ancestor (e.g. a<Subscription>
)Controllable
Components with ReduxControllable
componentSubscribable
: likereact-redux
, but betterSubscribable
makes it easy to create components with type-checked data and Store subscriptions using the Render Prop design pattern.Subscribable
is designed to handle any type of subscription. By far the most common use case is subscription to a Redux store, so that is what I will describe here. To see more advanced use cases, e.g. subscribing directly to an RxJSSubject
, check out the tests in Subscribable.spec.tsx.The best way to show how
Subscribable
can be used is by example.Standalone
<Subscription>
Using a
<Subscription>
component as a direct injection of state from a Redux store:// store definition import { createStoreContext } from 'stately-react' export const store = createStore(...) export const { Subscription } = createStoreContext(store) // elsewhere... <Subscription> {(state, dispatch) => <div> {/* render something interesting with state and dispatch */} </div>} </Subscription>
<Subscription>
with<Subscriber>
sTypically, you'll use the state of a Redux store or other subscription in many places throughout your application. For that, you'll want to incorporate the use of
<Subscriber>
descendants of the<Subscription>
component:// store definition import { createStoreContext } from 'stately-react' export const store = createStore(...) export const { Subscription, Subscriber } = createStoreContext(myStore) // In the root of your component tree: const MyApp = () => ( <Subscription> <div> {/* the rest of your app goes here */} </div> </Subscription> ) // Somewhere, a descendant component: const MyStateful = () => ( <Subscriber> {(state, dispatch) => <div> {/* render something interesting with state and dispatch */} </div>} </Subscriber> )
The
subscriber()
decoratorYou probably already have components whose props come directly from a subscription to a Redux store. The module
react-redux
uses theconnect()
high-order component to inject store state into components via props. Thesubscriber()
decorator is similar toconnect()
; however, since it was defined in the scope of yourStore
usingcreateStoreContext(myStore)
, it is capable of preserving type information, granting you confidence that you've mapped your state to props correctly:// store definition import { createStoreContext } from 'stately-react' const store = createStore(...) export const { Subscription, subscriber } = createStoreContext(store) // Component with props from Redux interface MyComponentProps { className: string, changeClassName: (newClass: string) => void } class MyComponent extends React.Component<MyComponentProps> { ... } // Apply the decorator const SubscriberMyComponent = subscriber( // Types are checked. (state, dispatch) => ({ className: state.className, changeClassName: (newClass: string) => { dispatch(changeClassNameAction(newClass)) } }) )(MyComponent) // elsewhere... const UsingMyComponent = () => ( // `className` and `changeClassName` do not need to be provided. // They have been injected by `subscriber()`. <SubscriberMyComponent /> )
Async
Components<Async>
and<CallableAsync>
areControllable
components, meaning they can operate either with or without a Redux store backing them. If noStore
is used, they will manage their own state internally using React'ssetState()
.In either case, the usage of the
<Async>
components is the same. To integrate with aStore
requires only that they are additionally wrapped with an<AsyncController>
. Many use cases do not require aController
, but some do, such as sharing the asynchronous state with other components, or custom handling of the AsyncActions.Integrating
<Async>
with the store will also allow you to see the actions and state mutations as they are dispatched in real-time using the Redux DevTools, which can be useful for debugging.Declarative
<Async>
operations<Async>
is "declarative", meaning that theparams
are passed in as a prop and the operation is performed automatically. It should be used whenever a component needs asynchronously-loaded data to render, such as search results or an entity from a REST service.This example uses
<Async>
to wrap a<SearchResults>
component, handling the execution and state management of the asynchronousdoSearch
operation:import { Async } from 'stately-react' import { doSearch } from './search' // type doSearch = (p1: number, p2: string) => Promise<SearchResults> <Async operation={doSearch} params={[123, 'abc']}>{ state => <div>{ state.error ? <ErrorMessage error={state.error} /> : state.data ? <SearchResults results={state.data} /> : state.status === 'active' ? <LoadingSpinner /> : null }</div> }</Async>
Reactive
<Async>
operations<CallableAsync>
is "reactive", meaning that you can initiate the call programatically, usually in response to a user interaction event.This example uses
<CallableAsync>
to invokesave
when the button is clicked:import { CallableAsync } from 'stately-react' import { save } from './saveEntity' // type save = (entity: Entity) => Promise <CallableAsync operation={save}>{ (state, callSave) => <div>{ state.error ? <ErrorMessage error={state.error} /> : state.status === 'complete' ? <SuccessMessage message="Entity saved successfully" /> : state.status === 'active' ? <LoadingSpinner /> : null }<EntityForm onSubmit={(entity: Entity) => callSave(entity)} /> </div> }</CallableAsync>
<Async>
operations with Redux<Async>
isControllable
, so it can be integrated with a Redux store by providing the store'sstate
anddispatch
to a parent<AsyncController>
:import { AsyncController, Async } from 'stately-react' import { Subscription } from './store' <Subscription>{ (state, dispatch) => <AsyncController state={state} dispatch={dispatch}> // Any component nested under the AsyncController <Async operation={doSearch} params={[123, 'abc']}>{ (state) => ... }</Async> </AsyncController> }</Subscription>
Controllable
componentsControllable
components manage their internal state with actions and reducers, allowing you to create components that can be used with or without a Store connection or external state management.Async
components areControllable
.By default,
<Controllable>
components create their own internalstate
anddispatch
wrapped around React'ssetState
system. By providing an ancestral<Controller>
, the consumer can provide a type-checked overridingstate
anddispatch
that will be used instead. Any actions triggered by the child are passed to the givendispatch
, and the component will render using the givenstate
.Controllable
components with ReduxIntegrating a
Controllable
component with a Redux store is a two-step process.First, the
Store
must be configured with the component'sreducer
(andmiddleware
, if present):import { createStore, applyMiddleware } from 'redux' import { merge } from 'stately-reducers' import { createStoreContext } from 'stately-react' import { controllableReducer, controllableMiddleware } from './MyControllable' import { myReducer } from './myReducer' export const store = createStore( merge( controllableReducer, myReducer ), // middleware is optional, can perform side effects applyMiddleware(controllableMiddleware) ) export const { Subscription, Subscriber, subscriber } = createStoreContext(store)
Second, a
<Controller>
corresponding to the<Controllable>
component must be placed somewhere in the component tree above a<Controllable>
component in question. EveryControllable
component must export a compatibleController
. For example,<AsyncController>
is exported alongside<Async>
.See the
<Async>
example.To take control of all
<Async>
components in your entire app, you might put this code at the root of your application:import { Subscription } from './store' <Subscription>{ (state, dispatch) => <AsyncController state={state} dispatch={dispatch}> {/* the rest of your app goes here */} </AsyncController> }</Subscription>
Once you have integrated the
<Controller>
, you can manage the<Controllable>
component's state however you wish. In the previous example,Async
actions are now being dispatched through theStore
, so you could, for example, create your ownreducer
to handle theasync/.../data
actions of an asynchronous call.Implementing a
Controllable
componentImplementing a
Controllable
component is easy, especially if you're familiar with React Context. To start with, you need to defineaction
s and areducer
to manage the state of your component. UsingcreateControllableContext()
, you can then create aControllable
/Controller
pair with thereducer
.<Controllable>
components can integrate middleware, as well:import { createControllableContext } from 'stately-async' const myReducer = ... export const { Controller, Controllable } = createControllableContext(myReducer, myMiddleware) // ... continued below
<Controllable>
will provide an internally-managedstate
anddispatch
utilizing the givenreducer
. Alternately, any parent consumer can use your<Controller>
to pass in thestate
anddispatch
. The children of the<Controllable>
can use these to render and update the state:export const MyControllable = props => ( <Controllable>{ // either coming from Controllable internally, or somewhere else! (state, dispatch) => <div> {/* implement your wonderful and amazing component here */ } </div> }</Controllable> )
Check out the implementation of Async.tsx for inspiration.