A dispatcher connects command names to the functions that handle them. A component can request an action without choosing a reducer itself: it dispatches a command, and the dispatcher calls the registered handlers. This makes the dispatcher the central routing piece of a small state manager. section 5.1.2 section 5.1.3
Build the mechanism around three operations:
- subscribe(commandName, handler) stores a handler under a command name and returns a function that unregisters it. Registering the same handler twice for one command does not add it twice. section 5.1.3
- dispatch(commandName, payload) calls each registered handler with the payload. If none are registered, it warns in the console. section 5.1.3
- afterEveryCommand(handler) registers a function to run after a command is handled, such as a function that renders the application again. It also returns an unregister function. section 5.1.3 section 5.2.3
The handlers are consumers: each takes the command’s payload and returns no value. A handler can wrap a reducer, which instead takes the current state and payload and returns new state. For example, a remove-todo handler receives an item index and assigns the result of removeTodo(state, index) back to state. section 5.1.3 section 5.1.2
Listing 5.4 Complete Dispatcher implementation (dispatcher.js)
export class Dispatcher {
#subs = new Map()
#afterHandlers = []
subscribe(commandName, handler) {
if (!this.#subs.has(commandName)) {
this.#subs.set(commandName, [])
}
const handlers = this.#subs.get(commandName)
if (handlers.includes(handler)) {
return () => {}
}
handlers.push(handler)
return () => {
const idx = handlers.indexOf(handler)
handlers.splice(idx, 1)
}
}
afterEveryCommand(handler) {
this.#afterHandlers.push(handler)
return () => {
const idx = this.#afterHandlers.indexOf(handler)
this.#afterHandlers.splice(idx, 1)
}
}
dispatch(commandName, payload) {
if (this.#subs.has(commandName)) {
this.#subs.get(commandName).forEach((handler) => handler(payload))
} else {
console.warn(`No handlers for command: ${commandName}`)
}
this.#afterHandlers.forEach((handler) => handler())
}
}
The complete Dispatcher class shows how a Map of command subscriptions, an array of after-command handlers, and returned unregister functions fit together. section 5.1.4
From a button click to an updated to-do list
A click handler dispatches 'remove-todo' with the item index. The state manager subscribes a handler that passes its payload to the removeTodo reducer and saves the returned state. An after-command handler then renders the application again. Components can use the shorter emit() function instead of calling dispatcher.dispatch() directly. section 5.2.3 section 5.2.4
To check the dispatcher on its own, register a 'greet' handler and an after-command handler. Dispatching 'greet' with 'John' should log “Hello, John!” followed by “Done greeting!” section 5.1.4
