Controller

Controller

Base class for controllers. Controllers are responsible for managing the certain part of the state, which includes:

  • Sending relevant actions in response to certain related events.
  • Modifying the state by providing the relevant reducer.
  • Providing access and if necessary transforming the data in the managed part of the state.

Controller is intended to be subclassed. It won't work as is, because it doesn't provide a reducer function (reducer() returns undefined).

The only function mandatory for overriding is:

  • reducer() - to provide the reducer function.

You may also need to override:

  • afterCreateStore() - will be called for all Controllers after Redux store was created and all Controllers were mounted.
  • areStatesEqual() - to control the optimizations done by Container. If you follow Redux recommendation of immutability and your Controller selector methods only transform the global state (which is also highly recommended), you do not need to override this method.

In order to utilize the full power of the framework, subclasses define two types of functions:

  • Selectors transform the part of the Controller-managed state into different structure. Any method that starts with $ symbol is considered to be a selector.
  • Dispatches are special functions that are expected to dispatch some actions either synchronously or asynchronously. Any method that starts with dispatch is considered to be a dispatch method.

Constructor

new Controller()

Constructor is a good place to create all actions that Controller needs. And of course to pass and store any external dependencies and parameters Controller needs to function.

Source:
Example
class ToDoController extends Controller {
  constructor() {
    super()
    this.createAction('add')
  }

  // Dispatch function. Will be mapped as `add(text)` in Container
  dispatchAdd(text) {
    this.dispatchAction('add', text)
  }

  // Selector function. Will be used to collect data for `props.text` in Container
  $texts(state) {
    return (this.$$(state)._items || []).map((item) => item.text)
  }

  reducer() {
    const { add } = this.actions
    return this.createReducer(
      add.on((state, text) => ({
        _items: (state._items || []).concat({ text })
      }))
    )
  }
}

Members

actions :Object.<string, Action>

Array of Actions previously created by Controller.createAction() calls.

Source:
Type:

dispatch :function

Redux store dispatch() function.

Source:
Type:
  • function

mountPath :Array.<string>

Path under which this controller is mounted as the array of keys.

Source:
Type:
  • Array.<string>

mountPathString :string

Path under which this controller is mounted as the single string. Path components are joined with . (dot) symbol.

Source:
Type:
  • string

store :Store

Redux store object this controller is mounted to.

Source:
Type:
  • Store

Methods

(static) is(instance)

Checks if provided value looks like an instance of the Controller class. It does pretty minimal check and should not be relied to actually detect the fact of being Controller's subclass if needed.

Source:
Parameters:
Name Type Description
instance

Value to be checked.

$(stateopt, pathopt)

Select the value at specified path of the stored state. If no path is specified (any falsey value or "*"), the full state of the tree is returned. All the required selector functions are called in both cases, first level keys in the state that start with underscore symbol (_) are considered "private" and ommitted.

Source:
Parameters:
Name Type Attributes Description
state Object <optional>

The root of the state tree managed by the Redux store. If ommitted, the function will operate on current state of the store.

path string | Array.<string> <optional>

The path of the sub tree to obtain from the state, relative to the controller mount path. It should either be a string of dot separated keys or an array of strings. Falsey value as well as not specifying this parameter makes the function to return the full state managed by the controller.

Returns:

Value selected from the specified path or undefined if nothing found at the specified path.

$$(state)

Get the raw part of the stored state, managed by the controller. No selectors will be called and no dispatches to be added to the result.

Source:
Parameters:
Name Type Description
state Object

The root of the state tree managed by the Redux store. If ommitted, the function will operate on current state of the store.

afterCreateStore()

Executed for all controllers after createStore() was called. At this point all of the controllers are created and store is initialized.

Source:

areStatesEqual($$prev, $$next)

This method is used by Controller.hasChanges by default. It checks if the state was changed comparing to an old state, so selectors need to be reevaluated. By default it compares state objects by reference (===). This should be fine if your state is immutable, which is highly recommended. Otherwise you are responsible for overriding this check according to your needs or just return false if you want reevaluate all selectors each time the state tree is updated.

Its purpose is basically the same as of options.areStatesEqual argument to connect function from react-redux library.

If you need to check the parts of the state, not managed by the controller, override Controller.hasChanges instead.

Source:
Parameters:
Name Type Description
$$prev

Previous value of part of the state managed by the Controller.

$$next

Next value part of the state managed by the Controller to be compared.

createAction(action, key)

Create new Action and attach it to Controller.actions. Intended to be used from inside the Controller. If provided a string key the Action will be created with type equal to ${Controller.mountPathString}/${action}.

Source:
Parameters:
Name Type Description
action string | Action

String to be used as action key in Controller#actions and as a part of Action.baseType. Alternatively the ready made Action can be specified to be attached to the Controller.

key string

If Action object was passed as first argument, this defines a key to be used in Controller.actions.

Example
// Create new action with key "update" and attach it to the Controller
this.createAction("update")

// Attach existing Action to the Controller using key "load"
this.createAction(loadAction, "load")

// Later on these actions are available in this.actions:
const { update, load } = this.actions

createReducer()

This a convenience function, which simply calls Action.createReducer() passing through all of the arguments.

Source:

dispatchAction(actionType, payload)

Dispatch the Action into the store by key and optionally a stage with the provided payload. This is a shortcut method provided for convenience. Is it intended to be used from inside the Controller.

Source:
Parameters:
Name Type Description
actionType string

Action key string with optional stage (see Action).

payload

Any object that should be sent as action payload.

Example
// Dispatch action with key "update"
dispatchAction("update", { objectId: "1" })

// Dispatch action with key "update" and stage "started"
dispatchAction("update.started", { objectId: "1" })

hasChanges(prevState, next)

This method is used by Container for optimizations. It checks if the state was changed comparing to an old state, so selectors need to be reevaluated. By default it calls Controller.areStatesEqual and returns the opposite boolean value.

It is useful, if controller selects parts of the state, not managed by itself.

Source:
Parameters:
Name Type Description
prevState

Previous Redux state value.

next

Next Redux state value.

reducer() → {function}

Called when Controller reducer is needed for the first time. Override this method and return the reducer function. Reducer function is executed on the part state where Controller was mounted. It is recommended to utilize Action and convenience functions Controller.createReducer, Controller.createAction and Controller.dispatchAction, but is not mandatory. A regular Redux reducer function will also work just fine.

Source:
Returns:
Type:
function

Reducer function.

Example
reducer() {
  const { update } = this.actions

  return this.createReducer(
    update.onStarted((state, payload) => ({...state, isUpdating: true })),
    update.onSuccess((state, items) => ({...state, items, isUpdating: false }))
  )
}

subscribe(path, listener)

Subscribes to changes of some value at path relative to the controller.

Source:
Parameters:
Name Type Description
path string | Array.<string>
listener Controller~SubscribeListener

Type Definitions

SubscribeListener(value, prevValue)

Passed as a callback to Controller.subscribe().

Source:
Parameters:
Name Type Description
value

Current value at the subscribed path.

prevValue

Previous value at the subscribed path.