Adding typed thunks
Easy Peasy exports a Thunk type, allowing you to declare a thunk against your model interface. The signature for this type is:
type Thunk<
Model extends Object = {},
Payload = void,
Injections = any,
StoreModel extends Object = {},
Result = any
>
Type parameters
As you can see it accepts five type parameters, all of them optional. This may seem like a lot, but in most cases you will likely only need to provide two. We have tried to order the type parameters from the most to the least frequently used.
The type parameters can be described as follows.
ModelThe model against which the thunk is being bound. This allows us to ensure the
actionsargument that is provided to our thunk implementations are correctly typed.PayloadIf you expect the thunk to receive a payload then you should provide the type for the payload. If your thunk will not receive any payload you can omit this type parameter or set it to
void.InjectionsWhen creating your store you can specify
injectionsvia the store configuration. The typical use case for theinjectionsis to provide a mechanism by which to dependency inject services used to make HTTP calls. Theseinjectionsare exposed to your your thunks via thehelpersargument that they receive.Should you be using injections then it is useful to provide the typing information that describes them to this type parameter.
StoreModelThe
helpersargument to your thunks exposes APIs which allow you to get the entire store state (viagetStoreState), or all the actions for your store (viagetStoreActions).For these to be correctly typed we need to ensure that we provide the model interface for our store to this type parameter.
ResultIf you return data from your thunk, then you should provide the expected type here.
A thunk always returns a
Promise. By default it is of typePromise<void>, however, if you provide this type parameter it becomesPromise<Result>.
Declaring a Thunk
Let's define a thunk which we will use to save a todo.
import { Thunk } from 'easy-peasy';
export interface TodosModel {
items: string[];
addTodo: Action<TodosModel, string>;
saveTodo: Thunk<TodosModel, string>; // 👈 declaring our thunk
}
As you can see our Thunk is operating against the TodosModel and it expects a payload of string.
Implementing a Thunk
We can now implement this thunk against our model.
import { thunk } from 'easy-peasy';
const todosModel: TodosModel = {
items: [],
addTodo: action((state, payload) => {
state.items.push(payload);
}),
saveTodo: thunk(async (actions, payload) => {
await todosService.save(payload); // imagine calling an HTTP service
actions.addTodo(payload);
})
};
You will have noted that TypeScript was providing us with the typing information and assertions whilst we implemented our thunk.
Typing info available during thunk implementationUsing a thunk
We can now consume the thunk within our component, making sure we use the typed version of useStoreActions that we exported from our store. We will refactor our component from earlier.
import { useStoreActions } from '../hooks'; // 👈 import typed hook
function AddTodo() {
// map the saveTodo thunk 👇
const saveTodo = useStoreActions(actions => actions.todos.saveTodo);
const [text, setText] = useState('');
const onButtonClick = useCallback(() => {
saveTodo(text) // 👈 dispatch our thunk with the text describing the todo
.then(() => setText('')); // then chain off the promise returned by the thunk
}, [saveTodo, setText, text]);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} type="text" />
<button onClick={onButtonClick}>Add Todo</button>
</>
);
}
Typing info available during thunk dispatchDemo Application
You can view the progress of our demo application here