Skip to content

Instantly share code, notes, and snippets.

@mrcrowl
Last active August 9, 2021 01:16
Show Gist options
  • Save mrcrowl/d7fd8d0369759a9fe315dbf27dc1bced to your computer and use it in GitHub Desktop.
Save mrcrowl/d7fd8d0369759a9fe315dbf27dc1bced to your computer and use it in GitHub Desktop.
Example of using vuex-type to create strongly-typed vuex store access
// 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 })
}
}
@antoine-pous
Copy link

import { getStoreBuilder } from "vuex-typex"; is missing in store.ts. Without this import TypeScript throw this error: TS2304: Cannot find name 'getStoreBuilder'.

@christopher-kiss
Copy link

christopher-kiss commented Mar 27, 2018

@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.

@dmarg
Copy link

dmarg commented Apr 24, 2018

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?

@Kosqe
Copy link

Kosqe commented Jul 31, 2018

Wow! This works like magic to me. Good work!

@SvenPam
Copy link

SvenPam commented Aug 22, 2018

@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.

@tqwewe
Copy link

tqwewe commented Sep 2, 2018

@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'

@Mart-Bogdan
Copy link

Mart-Bogdan commented Oct 22, 2018

It don't work with SSR!

All sessions gets same store!

@jp8998
Copy link

jp8998 commented Dec 14, 2018

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?

@jp8998
Copy link

jp8998 commented Dec 14, 2018

Figured it out context: ActionContext<BasketState, RootState> needs changing to context: BareActionContext<BasketState, RootState>

@mrcrowl
Copy link
Author

mrcrowl commented Jan 22, 2019

It don't work with SSR!

All sessions gets same store!

Yes, sorry. Never tried this with SSR tbh.

@mrcrowl
Copy link
Author

mrcrowl commented Jan 22, 2019

@Schteele.. thanks. I've updated it to use BareActionContext now.

@bishopsmove
Copy link

bishopsmove commented Mar 19, 2019

@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'

Thanks for that, @acidic9. That worked nicely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment