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
paths by passing thepathsproperty to the provider to ensure invalidation only occurs when relevant state changes. - A catch-all when no
paths 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
beforemiddleware 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
aftermiddleware 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.
});