A strongly-opinionated Yeoman generator for scaffolding out a redux reducer (and associated state slice & api calls) based on the way that @doches likes to set things up. Probably not useful for you, but tremendously useful for him.
Install Yeoman via yarn, or npm, or whatever JS package manager is hot this week:
$ yarn global add yo
Once you've got Yeoman set up, install this generator:
$ yarn global add generator-tfountain-redux-reducer
Move into the directory in your project where you want to create the new redux slice, and invoke Yeoman:
$ cd awesome-project/src/state
$ yo tfountain-redux-reducer
Tell Yeoman about your type name for this reducer, then sit back and bask in the glory of a lovely autogenerated piece of boilerplate that you didn't have to write. Finish your application. Move on with your life. Maybe write that novel you've always been dreaming about, or start learning the drums.
Go on. Live a little.
By way of example, the generator will ask you for singluar (e.g. "item") and plural (e.g. "items") type names, then produce a file like this:
import { Dispatch } from "redux";
import { baseURL, headers } from "./api";
export interface IItemsState {
fetchPending: boolean;
fetched: boolean;
updatePending: boolean;
items: IItem[];
}
export const DEFAULT_ITEMS_STATE: IItemsState = {
fetchPending: false,
fetched: false,
updatePending: false,
items: [],
}
export const SET_FETCH_PENDING = "@@items/SET_FETCH_PENDING";
export const SET_UPDATE_PENDING = "@@items/SET_UPDATE_PENDING";
export const SET_ITEMS = "@@items/SET_ITEMS";
export const ADD_ITEM = "@@items/ADD_ITEM";
export const REMOVE_ITEM = "@@items/REMOVE_ITEM";
export const UPDATE_ITEM = "@@items/UPDATE_ITEM";
export function setFetchPending(pending: boolean) {
return {
type: SET_FETCH_PENDING,
pending,
};
}
export function setUpdatePending(pending: boolean) {
return {
type: SET_UPDATE_PENDING,
pending,
};
}
export function setItems(items: IItem[]) {
return {
type: SET_ITEMS,
items,
};
}
export function addItem(item: IItem) {
return {
type: ADD_ITEM,
item,
};
}
export function removeItem(itemId: number) {
return {
type: REMOVE_ITEM,
id: itemId,
};
}
function replaceItem(item: IItem) {
return {
type: UPDATE_ITEM,
item,
};
}
export function createItem(item: IItem) {
return (dispatch: Dispatch<any>) => {
dispatch(setUpdatePending(true));
return fetch(`${baseURL}items/create`, {
headers,
method: "POST",
body: JSON.stringify(item),
})
.then((response: Response) => {
if (!response.ok) {
throw new Error();
}
return response.json();
})
.then((created: IItem) => {
dispatch(addItem(created));
})
.catch((error: any) => {
console.error(error);
})
.finally(() => {
dispatch(setUpdatePending(false));
});
}
}
export function listItems() {
return (dispatch: Dispatch<any>) => {
dispatch(setFetchPending(true));
return fetch(`${baseURL}items/list`, {
headers,
method: "GET",
})
.then((response: Response) => {
if (!response.ok) {
throw new Error();
}
return response.json();
})
.then((items: IItem[]) => {
dispatch(setItems(items));
})
.catch((error: any) => {
console.error(error);
})
.finally(() => {
dispatch(setFetchPending(false));
});
}
}
export function updateItem(item: IItem) {
return (dispatch: Dispatch<any>) => {
dispatch(setUpdatePending(true));
return fetch(`${baseURL}items/update`, {
headers,
method: "POST",
body: JSON.stringify(item),
})
.then((response: Response) => {
if (!response.ok) {
throw new Error();
}
dispatch(replaceItem(item));
})
.catch((error: any) => {
console.error(error);
})
.finally(() => {
dispatch(setUpdatePending(false));
});
}
}
export function deleteItem(itemId: number) {
return (dispatch: Dispatch<any>) => {
dispatch(setUpdatePending(true));
return fetch(`${baseURL}items/${itemId}/delete`, {
headers,
method: "POST",
})
.then((response: Response) => {
if (!response.ok) {
throw new Error();
}
dispatch(removeItem(itemId));
})
.catch((error: any) => {
console.error(error);
})
.finally(() => {
dispatch(setUpdatePending(false));
});
}
}
export function items(state: IItemsState, action: any) {
if (!state) {
state = DEFAULT_ITEMS_STATE;
}
switch (action.type) {
default:
return state;
case SET_FETCH_PENDING:
return {
...state,
fetchPending: action.pending,
};
case SET_UPDATE_PENDING:
return {
...state,
updatePending: action.pending,
};
case SET_ITEMS:
return {
...state,
fetchPending: false,
fetched: true,
items: action.items,
};
case ADD_ITEM:
return {
...state,
updatePending: false,
items: [
...state.items,
action.item,
],
};
case REMOVE_ITEM:
return {
...state,
updatePending: false,
items: state.items.filter((item: IItem) => item.id != action.item.id),
};
case UPDATE_ITEM:
return {
...state,
updatePending: false,
items: state.items.map((item: IItem) => item.id === action.item.id ? action.item : item),
};
}
}