StoreApi
Defines an api around a Zedux store or other observable. Used to create component-bound stores (stores that live and die with a React component).
Definition
class StoreApi<TState> {
  _bindControls(
    StoreApiClass: StoreApiConstructor<TState>
  ): StoreApi<TState>
}
interface StoreApi<TState> {
  store: Observable<TState>
}
See the StoreApiConstructor and Observable types.
The gist
To create a component-bound store simply create a class that extends StoreApi. This class must have a visible store instance property whose value is an Observable.
The class may also take static actors and selectors properties. See the StoreApiConstructor type for more details.
This class constructor may be passed directly to createContext(). React Zedux will instantiate it every time the resulting Context's <Provider> (or <Injector>) is mounted. React Zedux will also call storeApi._bindControls() to bind any actors/selectors to the store and merge those and all properties of the store into the api instance itself.
React Zedux will throw an error if there are any duplicate keys between the actors map, the selectors map, and the store's own properties. This mitigates the problems associated with this "mixin" sort of behavior.
Examples
A simple component-bound store.
import React from 'react'
import { StoreApi, createContext } from 'react-zedux'
import { createStore } from 'zedux'
class CounterApi extends StoreApi {
  /*
    The `store` property is required. React Zedux will throw
    an error if it isn't a valid observable.
    Here we're creating a new store every time the CounterApi is
    instantiated. This will create a component-bound store. Note
    that we could set it to a global store if we just want to
    take advantage of the other features offered by StoreApis.
  */
  store = createStore().hydrate(0)
}
/*
  We pass the class constructor directly to `createContext()`.
  React Zedux will instantiate it every time a <Provider>
  is mounted.
*/
const CounterContext = createContext(CounterApi)
// Some components. Part of a complete example.
const Counter = () => (
  <CounterContext.Injector>
    {({ state }) => state}
  </CounterContext.Injector>
)
// Now every time we mount a <Counter>, React Zedux creates
// a new counter store.
const App = () => (
  <>
    <Counter />
    <Counter />
  </>
)
The StoreApi is useful for defining a store's api (hence the name...). The "api" is the interface consumers use to interact with the store. The StoreApi can bind actors and selectors to the store and provide hooks and other utilities for accessing and modifying the store's data.
import React from 'react'
import { StoreApi, createContext } from 'react-zedux'
import { createStore, select } from 'zedux'
class TodosApi extends StoreApi {
  /*
    A StoreApi can take static `actors` and `selectors` properties.
    These will be bound to the store and merged into the TodosApi
    instance.
  */
  static actors = {
    // A simple inducer factory
    addTodo: text => state => [
      ...state,
      { text, isComplete: false }
    ]
  }
  static selectors = {
    // A simple, memoized selector
    selectIncompleteTodos: select(
      state => state.filter(todo => todo.isComplete)
    )
  }
  store = createStore().hydrate([])
}
const TodosContext = createContext(TodosApi)
// Now when we mount a TodosContext.Provider, React Zedux will bind
// these actors and selectors to the store and merge them into the
// api instance.
const Todos = () => (
  <TodosContext.Injector>
    {({ addTodo, selectIncompleteTodos }) => {
      addTodo('be awesome')
      selectIncompleteTodos()
      // [ { text: 'be awesome' isComplete: false } ]
    }}
  </TodosContext.Injector>
)
The actors and selectors can contain nested namespaces. React Zedux will preserve the nesting. This can be useful for creating hooks (such as actors that perform a check before proceeding with a dispatch):
import React from 'react'
import { StoreApi, createContext } from 'react-zedux'
class TodosApi extends StoreApi {
  static actors = {
    wrappedActors: {
      addTodo: text => state => [
        ...state,
        { text, isComplete: false }
      ]
    }
  }
  store = createStore().hydrate([])
  addTodo(text) {
    const todos = this.store.getState()
    if (todos.some(todo => todo.text === text)) {
      return todos // a todo already exists with that name
    }
    // We're good; proceed with the dispatch
    return this.wrappedActors.addTodo(text)
  }
}
const Todos = () => (
  <TodosContext.Injector>
    {({ addTodo }) => {
      addTodo('nest a selector now')
    }}
  </TodosContext.Injector>
)
Testing
Just be sure to call storeApi._bindControls() manually:
import { StoreApi } from 'react-zedux'
import { createStore } from 'zedux'
class TodosApi extends StoreApi {
  static actors = {
    addTodo: text => state => [
      ...state,
      { text, isComplete: false }
    ]
  }
  store = createStore().hydrate([])
}
const todosApi = new TodosApi()
  ._bindControls() // yes, it can be chained
todosApi.addTodo('a')
expect(todosApi.getState()).toBe([
  { text: 'a', isComplete: false }
])
Method Api
_bindControls()
Definition
() => StoreApi<TState>
Returns the StoreApi instance for chaining.
Explanation
Given a StoreApi instance like so:
import { StoreApi } from 'react-zedux'
import { createStore } from 'zedux'
class CounterApi extends StoreApi {
  store = createStore().hydrate(0)
  static actors = {
    increment: () => state => state + 1
  }
  static selectors = {
    selectNumTimesTen: state => state * 10
  }
}
const counterApi = new CounterApi()
calling
counterApi._bindControls()
Will:
- Merge all properties of - counterApi.storeinto- counterApi. Will throw an error if any of those properties already exist on- counterApi.
- Bind the - incrementactor to the store.
- Stick the bound - incrementactor on- counterApi. Will throw an error if- incrementalready exists on- counterApi.
- Bind the - selectNumTimesTenselector to the store.
- Stick the bound - selectNumTimesTenselector on- counterApi. Will throw an error if- selectNumTimesTenalready exists on- counterApi.
Notes
You don't have to use the auto-binding feature of StoreApis. The following two examples are equivalent:
import { StoreApi } from 'react-zedux'
import { createStore } from 'zedux'
class CounterApi extends StoreApi {
  store = createStore().hydrate(0)
  increment() {
    this.store.dispatch(state => state + 1) // or this.dispatch(...)
  }
}
and:
import { StoreApi } from 'react-zedux'
import { createStore } from 'zedux'
class CounterApi extends StoreApi {
  store = createStore().hydrate(0)
  static actors = {
    increment: () => state => state + 1
  }
}