@andrewbrennanfr/burrito v0.5.1
๐ฏ Burrito
Build a Vuex store that is strictly formatted, strongly typed & unit tested ๐ฅณ
๐งถ Installation
NPM
npm install @andrewbrennanfr/burrito
Yarn
yarn add @andrewbrennanfr/burrito
Import
import Burrito, { Item, Module, Status } from "@andrewbrennanfr/burrito";
๐ Usage
Suppose we are building a comments feature, allowing users to write comments on a post.
First we must make a commentsModule
to use in the Vuex store.
import Burrito from "@andrewbrennanfr/burrito";
export type Comment = {
body: string;
id: string;
postId: string;
};
export type CommentFetchParams = {
postId: string;
};
export const commentsModule = Burrito.module<Comment, CommentFetchParams>(
{
getDataKey: (comment) => comment.id,
getParamsKey: (params) => params.postId,
},
{
add: (comment) =>
new Promise<Comment>((resolve, reject) => {
/**
* Perform API request to add a comment
*
* If successful: resolve(Comment)
* Otherwise: reject(string)
*/
}),
edit: (oldComment, newComment) =>
new Promise<Comment>((resolve, reject) => {
/**
* Perform API request to edit a comment
*
* If successful: resolve(Comment)
* Otherwise: reject(string)
*/
}),
fetch: (params) =>
new Promise<Comment[]>((resolve, reject) => {
/**
* Perform API request to fetch comments
*
* If successful: resolve(Comment[])
* Otherwise: reject(string)
*/
}),
refetch: (params) =>
new Promise<Comment[]>((resolve, reject) => {
/**
* Perform API request to refetch comments
*
* If successful: resolve(Comment[])
* Otherwise: reject(string)
*/
}),
remove: (comment) =>
new Promise<void>((resolve, reject) => {
/**
* Perform API request to remove a comment
*
* If successful: resolve(void)
* Otherwise: reject(string)
*/
}),
}
);
This module can be used like any other Vuex module.
import { commentsModule } from "/path/to/commentsModule";
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
actions: {},
modules: {
comments: commentsModule,
},
mutations: {},
state: {},
});
Next we will make this available to our Vue components.
import Burrito from "@andrewbrennanfr/burrito";
import Vue from "vue";
Vue.use(Burrito);
Components can now access the module's data by referencing this.$modules.comments
.
The $modules
plugin exposes the following information about comments:
type Module = {
actions: {
add: (data: Comment) => void;
clear: (getClearCondition: (data: Comment) => boolean) => void;
edit: (oldData: Comment, newData: Comment) => void;
fetch: (params: CommentFetchParams) => void;
refetch: (params: CommentFetchParams) => void;
remove: (data: Comment) => void;
};
state: {
items: {
[commentId: string]:
| {
data: Comment;
status: Error | "adding" | "editing" | "removing" | null;
}
| undefined;
};
list: {
data: Comment;
status: Error | "adding" | "editing" | "removing" | null;
}[];
listGroupedBy: Partial<
Record<
string,
{
data: Comment;
status: Error | "adding" | "editing" | "removing" | null;
}[]
>
>;
listSortedBy: {
data: Comment;
status: Error | "adding" | "editing" | "removing" | null;
}[];
status: {
[postId: string]:
| Error
| "fetched"
| "fetching"
| "refetched"
| "refetching"
| undefined;
};
};
};
Lastly, we must add the type definition, so that typescript understands the structure of commentsModule
.
import { Module } from "@andrewbrennanfr/burrito";
declare module "vue/types/vue" {
interface Vue {
$modules: {
comments: Module<Comment, CommentsFetchParams>;
};
}
}
That's it! ๐ฅณ Our commentsModule
is available, with full type safety, in our Vue components.
Now the actions (add
, edit
, fetch
, refetch
, remove
) automatically synchronize commentsModule
with the API.
Consider the following example:
@Component
export default class App extends Vue {
private get comment() {
return this.$modules.comments.state.items[this.commentId];
}
private handleUpdateCommentText() {
this.$modules.comments.actions.edit(this.comment, {
...this.comment,
body: "Goodbye everyone!",
});
}
}
Initially, the value of this.comment
would look something like:
{
data: {
body: "Hello world!",
id: "123",
postId: "456",
},
status: null,
};
By calling this.handleUpdateCommentText
, our comment would be sent to the API & in the meantime would optimistically update to look like:
{
data: {
- body: "Hello world!",
+ body: "Goodbye everyone!",
id: "123",
postId: "456",
},
- status: null,
+ status: "editing",
};
The UI can be updated to reflect the new optimistic state of the comment & show the current editing
status.
If the dispatched API request resolves successfully, the comment will revert back to status: null
.
If the request is rejected, the values status
will reflect this too.
The fetching
/refetching
status for a set of comments on a post, can be determined by accessing this.$modules.comments.state.status[postId]
.
๐ Example
To try a live example, run the commands below & open localhost:8080 in your browser.
git clone git@github.com:andrewbrennanfr/burrito.git
cd burrito
yarn install
yarn serve
๐ฃ FAQ
What is the problem that you're trying to solve?
This library provides an opinionated design pattern, for storing application state & interacting with an API. It handles common behaviors like optimistic updating & error handling out-of-the-box, reducing boilerplate. The goal is to offer a type-safe way to interact with the the Vuex store, that is predictable & well tested.
What is the difference between fetch
& refetch
?
It's expected that both return arrays of data.
However, fetch
will merge the new items into the existing data in the store & refetch
will remove all existing items, before applying the new items to the store.
Why did you call the library Burrito
?
Who doesn't love burritos? ๐ ๐ฏ
โป๏ธ Versioning
This library aims to follow the SemVer (MAJOR.MINOR.PATCH
) convention.
In short, this means:
MAJOR
versions (v1.0.0 => v2.0.0
) will be incremented for breaking changesMINOR
versions (v1.0.0 => v1.1.0
) will be incremented for non-breaking changesPATCH
versions (v1.0.0 => v1.0.1
) will be incremented for tweaks, bug fixes & documentation changes
โ ๏ธ Please note, before v1.0.0 all MINOR
& PATCH
increments should be considered breaking!
๐ License
Copyright (c) 2021 Andrew Brennan