Store concepts in detail
State
object
In modern browsers a state
object is passed as part of the CommandRequest
. Any modification to this state
object gets translated to the appropriate patch operations and applied against the store.
import { createCommandFactory } from '@dojo/framework/stores/process';
import { State } from './interfaces';
import { remove, replace } from '@dojo/framework/stores/state/operations';
const createCommand = createCommandFactory<State>();
const addUser = createCommand<User>(({ payload, state }) => {
const currentUsers = state.users.list || [];
state.users.list = [...currentUsers, payload];
});
Note that attempting to access state is not supported in IE11 and will immediately throw an error.
StoreProvider
The StoreProvider accepts three properties
renderer
: A render function that has the store injected to access state and pass processes to child widgets.stateKey
: The key of the state in the registry.paths
(optional): A function to connect the provider to sections of the state.
Invalidation
The StoreProvider
has two main ways to trigger invalidation and cause a re-render.
- The recommended approach is to register
path
s by passing thepaths
property to the provider to ensure invalidation only occurs when relevant state changes. - A catch-all when no
path
s are defined for the container, it will invalidate when any data changes in the store.
Process
Lifecycle
A Process
has an execution lifecycle that defines the flow of the behavior being defined.
- If a transformer is present it gets executed first to transform the payload
before
middleware get executed synchronously in order- commands get executed in the order defined
- operations get applied from the commands after each command (or block of commands in the case of multiple commands) gets executed
- If an exception is thrown during commands, no more commands get executed and the current set of operations are not applied
after
middleware get executed synchronously in order
Transformers
By using a transformer, you can modify a payload before it is used by a processes commands. This is a way to create additional process executors that accept different payloads.
interface PricePayload {
price: number;
}
const createCommand = createCommandFactory<any, PricePayload>();
// `payload` is typed to `PricePayload`
const setNumericPriceCommand = createCommand(({ get, path, payload }) => {});
const setNumericPrice = createProcess('set-price', [setNumericPriceCommand]);
First, create a transformer to convert another type of input into PricePayload
:
interface TransformerPayload {
price: string;
}
// The transformer return type must match the original `PricePayload`
const transformer = (payload: TransformerPayload): PricePayload => {
return {
price: parseInt(payload.price, 10)
};
};
Now simply create the process with this transformer.
const processExecutor = setNumericPrice(store);
const transformedProcessExecutor = setNumericPrice(store, transformer);
processExecutor({ price: 12.5 });
transformedProcessExecutor({ price: '12.50' });
Process middleware
Middleware gets applied around processes using optional before
and after
methods. This allows for generic, sharable actions to occur around the behavior defined by processes.
Multiple middlewares may get defined by providing a list. Middlewares get called synchronously in the order listed.
Before
A before
middleware block gets passed a payload
and a reference to the store
.
middleware/beforeLogger.ts
const beforeOnly: ProcessCallback = () => ({
before(payload, store) {
console.log('before only called');
}
});
After
An after
middleware block gets passed an error
(if one occurred) and the result
of a process.
middleware/afterLogger.ts
const afterOnly: ProcessCallback = () => ({
after(error, result) {
console.log('after only called');
}
});
The result
implements the ProcessResult
interface to provide information about the changes applied to the store and provide access to that store.
executor
- allows additional processes to run against the storestore
- a reference to the storeoperations
- a list of applied operationsundoOperations
- a list of operations that can be used to reverse the applied operationsapply
- the apply method from the storepayload
- the provided payloadid
- the id used to name the process
Subscribing to store changes
The Store
has an onChange(path, callback)
method that takes a path or an array of paths and invokes a callback function whenever that state changes.
main.ts
const store = new Store<State>();
const { path } = store;
store.onChange(path('auth', 'token'), () => {
console.log('new login');
});
store.onChange([path('users', 'current'), path('users', 'list')], () => {
// Make sure the current user is in the user list
});
The Store
also has an invalidate
event that fires any time the store changes.
main.ts
store.on('invalidate', () => {
// do something when the store's state has been updated.
});