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.
Model
The model against which the thunk is being bound. This allows us to ensure the
actions
argument that is provided to our thunk implementations are correctly typed.Payload
If 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
.Injections
When creating your store you can specify
injections
via the store configuration. The typical use case for theinjections
is to provide a mechanism by which to dependency inject services used to make HTTP calls. Theseinjections
are exposed to your your thunks via thehelpers
argument that they receive.Should you be using injections then it is useful to provide the typing information that describes them to this type parameter.
StoreModel
The
helpers
argument 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.
Result
If 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.
Using 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>
</>
);
}
Demo Application
You can view the progress of our demo application here