-
-
Save mrcrowl/d7fd8d0369759a9fe315dbf27dc1bced to your computer and use it in GitHub Desktop.
// path: store/basket/basket.ts (module) | |
import { RootState } from "../../store" | |
import inventory, { Product } from "../inventory/inventory" | |
export interface Item { productId: string, quantity: number } | |
export interface DisplayItem { product: Product, quantity: number } | |
export interface BasketState { items: Item[], isLoading: boolean } | |
const initialBasketState: BasketState = { items: [], isLoading: false } | |
const b = getStoreBuilder<RootState>().module("basket", initialBasketState) | |
// getters | |
const numberOfItemsGetter = b.read(state => state.items.length, "numberOfItems") | |
const itemsGetter = b.read(state => | |
{ | |
const displayItems: DisplayItem[] = state.items.map(item => | |
{ | |
return { | |
product: inventory.getProductById(item.productId), | |
quantity: item.quantity | |
} | |
}) | |
return displayItems | |
}) | |
// mutations | |
function appendItem(state: BasketState, payload: { productId: string, quantity: number }) | |
{ | |
state.items.push({ | |
productId: payload.productId, | |
quantity: payload.quantity | |
}) | |
} | |
function setIsLoading(state: BasketState, payload: { isLoading: boolean }) | |
{ | |
state.isLoading = payload.isLoading | |
} | |
// action | |
async function restoreSavedBasket(context: BareActionContext<BasketState, RootState>) | |
{ | |
const savedBasketId = localStorage["basketId"] | |
try | |
{ | |
basket.commitSetIsLoading({ isLoading: true }) | |
const { data: savedBasket } = await axios.get(`//chips-store.com/get-saved-basket/${savedBasketId}`, { responseType: "json" }) | |
const items: Item[] = savedBasket.items | |
items.forEach(item => basket.commitAppendItem(item)) | |
} | |
finally | |
{ | |
basket.commitSetIsLoading({ isLoading: false }) | |
} | |
} | |
// state | |
const stateGetter = b.state() | |
// exported "basket" module interface | |
const basket = { | |
// state | |
get state() { return stateGetter() }, | |
// getters (wrapped as real getters) | |
get items() { return itemsGetter() }, | |
get numberOfItems() { return numberOfItemsGetter() }, | |
// mutations | |
commitAppendItem: b.commit(appendItem), | |
commitSetIsLoading: b.commit(setIsLoading), | |
// actions | |
dispatchRestoreSavedBasket: b.dispatch(restoreSavedBasket) | |
} | |
export default basket |
// path: store/inventory/inventory.ts (module) | |
import { getStoreBuilder, BaseActionContext } from "vuex-typex" | |
import { Store } from "vuex" | |
import { RootState } from "../../store" | |
import axios from "axios" | |
export interface InventoryState { productsById: { [productId: string]: Product } } | |
export interface Product { id: string, name: string } | |
const initialInventoryState: InventoryState = { | |
productsById: { | |
"fritos": { id: "fritos", name: "Fritos Corn Chips, Chili Cheese" }, | |
"doritos": { id: "doritos", name: "Doritos Nacho Cheese Flavored Tortilla Chips" }, | |
"cheetos": { id: "cheetos", name: "Cheetos Crunchy Cheese Flavored Snacks" }, | |
"tostitos": { id: "tostitos", name: "Tostitos Original Restaurant Style Tortilla Chips" } | |
} | |
} | |
const p = getStoreBuilder<RootState>().module("product", initialInventoryState) | |
const getProductByIdGetter = p.read(state => (id: string) => state.productsById[id], "getProductById") | |
// state | |
const stateGetter = p.state() | |
// exported "inventory" module interface | |
const inventory = { | |
// state | |
get state() { return stateGetter() }, | |
// getter as method | |
getProductById(id: string) | |
{ | |
return getProductByIdGetter()(id) | |
} | |
} | |
export default inventory |
// path: store/store.ts (root store definition) | |
import Vue from 'vue' | |
import Vuex, { Store } from 'vuex' | |
import inventory from "./inventory/inventory" | |
import basket from "./basket/basket" | |
export interface RootState | |
{ | |
basket: BasketState | |
inventory: InventoryState | |
} | |
Vue.use(Vuex) | |
const store: Store<RootState> = getStoreBuilder<RootState>().vuexStore() | |
export default store // <-- "store" to provide to root Vue |
// path: app.ts (root Vue) | |
import Vue from 'vue' | |
import Vuex from 'vuex' | |
import store from './store/store' | |
import app_html from './app.html' | |
const app = new Vue({ | |
el: '#app', | |
template: app_html, | |
store | |
}) | |
export default app |
// path: components/basket/basketDisplay.ts (component) | |
import basket from "../../store/basket/basket" | |
@Component({ template: basket_display_html }) | |
export class BasketDisplay extends Vue | |
{ | |
get isLoading() { return basket.state.isLoading } | |
get items() { return basket.items } | |
get numberOfItems() { return basket.numberOfItems } | |
restoreSavedBasket() | |
{ | |
basket.dispatchRestoreSavedBasket() | |
} | |
addToBasket(productId: string, quantity: number = 1) | |
{ | |
basket.commitAppendItem({ productId, quantity }) | |
} | |
} |
@b12f, I ran into the same issue with the modules being shaken out by typescript. Trying to use the module in the router, caused issues since it wasn't defined.
One solution I came up with, since I'm not using the module in the store.ts, was to import the module for side-effects. This stops typescript from shaking them out.
This means changing
// store.ts
import inventory from "./inventory/inventory"
import basket from "./basket/basket"
to
// store.ts
import "./inventory/inventory"
import "./basket/basket"
If you check here: https://www.typescriptlang.org/docs/handbook/modules.html and look for "Import a module for side-effects only"
Hope this helps others.
I'm getting the following error:
"Can't add module after vuexStore() has been called"
Anybody else getting that? Any suggestions on how to get passed that?
Wow! This works like magic to me. Good work!
@dmarg, did you ever solve this?
Trying to migrate to VueCLI 3, and I am seeing the same error on the same codebase which makes me think the issue is with the CLI -- or more the upgrade to Typescript 3.x.
@SvenPam also having this issue.. any ideas?
Edit
I fixed this by changing my store.ts file from:
import { AuthState } from './modules/auth'
to
import { AuthState } from './modules/auth'
import './modules/auth'
It don't work with SSR!
All sessions gets same store!
In this example I get the error:
70:44 Argument of type '(context: ActionContext<BasketState, RootState>) => Promise<void>' is not assignable to parameter of type 'ActionHandler<BasketState, RootState, {}, void>'.
when I try to use your basket state as you have it setup. Any thoughts?
@Gregoyle I ran into the same error, did you ever find out why you got this error?
Figured it out context: ActionContext<BasketState, RootState> needs changing to context: BareActionContext<BasketState, RootState>
It don't work with SSR!
All sessions gets same store!
Yes, sorry. Never tried this with SSR tbh.
@Schteele.. thanks. I've updated it to use BareActionContext
now.
import { getStoreBuilder } from "vuex-typex";
is missing instore.ts
. Without this import TypeScript throw this error:TS2304: Cannot find name 'getStoreBuilder'
.