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
SubscribableComponents: Components that create type-safe subscriptions (e.g. to a Redux store)<Subscription><Subscription>with<Subscriber>sThe subscriber()decoratorAsyncComponents: Components that perform type-safe asynchronous operations, manage their state, and provide status updates (active, completed, error)<Async>operations<Async>operations<Async>operations with ReduxControllableComponents: Components that use a reducer to manage internal state, or can defer their state to a controlling ancestor (e.g. a<Subscription>)ControllableComponents with ReduxControllablecomponentSubscribable: likereact-redux, but betterSubscribablemakes it easy to create components with type-checked data and Store subscriptions using the Render Prop design pattern.Subscribableis 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
Subscribablecan 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-reduxuses 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 yourStoreusingcreateStoreContext(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 /> )AsyncComponents<Async>and<CallableAsync>areControllablecomponents, meaning they can operate either with or without a Redux store backing them. If noStoreis 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 aStorerequires 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 theparamsare 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 asynchronousdoSearchoperation: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 invokesavewhen 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'sstateanddispatchto 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>ControllablecomponentsControllablecomponents 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.Asynccomponents areControllable.By default,
<Controllable>components create their own internalstateanddispatchwrapped around React'ssetStatesystem. By providing an ancestral<Controller>, the consumer can provide a type-checked overridingstateanddispatchthat will be used instead. Any actions triggered by the child are passed to the givendispatch, and the component will render using the givenstate.Controllablecomponents with ReduxIntegrating a
Controllablecomponent with a Redux store is a two-step process.First, the
Storemust 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. EveryControllablecomponent 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,Asyncactions are now being dispatched through theStore, so you could, for example, create your ownreducerto handle theasync/.../dataactions of an asynchronous call.Implementing a
ControllablecomponentImplementing a
Controllablecomponent is easy, especially if you're familiar with React Context. To start with, you need to defineactions and areducerto manage the state of your component. UsingcreateControllableContext(), you can then create aControllable/Controllerpair 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-managedstateanddispatchutilizing the givenreducer. Alternately, any parent consumer can use your<Controller>to pass in thestateanddispatch. 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.