@andrewbrennanfr/burrito v0.5.1
๐ฏ Burrito
Build a Vuex store that is strictly formatted, strongly typed & unit tested ๐ฅณ
๐งถ Installation
NPM
npm install @andrewbrennanfr/burritoYarn
yarn add @andrewbrennanfr/burritoImport
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:
MAJORversions (v1.0.0 => v2.0.0) will be incremented for breaking changesMINORversions (v1.0.0 => v1.1.0) will be incremented for non-breaking changesPATCHversions (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