Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
2.7 KiB
Markdown
115 lines
2.7 KiB
Markdown
---
|
|
name: composables-in-stores
|
|
description: Using Vue composables within Pinia stores
|
|
---
|
|
|
|
# Composables in Stores
|
|
|
|
Pinia stores can leverage Vue composables for reusable stateful logic.
|
|
|
|
## Option Stores
|
|
|
|
Call composables inside the `state` property, but only those returning writable refs:
|
|
|
|
```ts
|
|
import { defineStore } from 'pinia'
|
|
import { useLocalStorage } from '@vueuse/core'
|
|
|
|
export const useAuthStore = defineStore('auth', {
|
|
state: () => ({
|
|
user: useLocalStorage('pinia/auth/login', 'bob'),
|
|
}),
|
|
})
|
|
```
|
|
|
|
**Works:** Composables returning `ref()`:
|
|
- `useLocalStorage`
|
|
- `useAsyncState`
|
|
|
|
**Doesn't work in Option Stores:**
|
|
- Composables exposing functions
|
|
- Composables exposing readonly data
|
|
|
|
## Setup Stores
|
|
|
|
More flexible - can use almost any composable:
|
|
|
|
```ts
|
|
import { defineStore } from 'pinia'
|
|
import { useMediaControls } from '@vueuse/core'
|
|
import { ref } from 'vue'
|
|
|
|
export const useVideoPlayer = defineStore('video', () => {
|
|
const videoElement = ref<HTMLVideoElement>()
|
|
const src = ref('/data/video.mp4')
|
|
const { playing, volume, currentTime, togglePictureInPicture } =
|
|
useMediaControls(videoElement, { src })
|
|
|
|
function loadVideo(element: HTMLVideoElement, newSrc: string) {
|
|
videoElement.value = element
|
|
src.value = newSrc
|
|
}
|
|
|
|
return {
|
|
src,
|
|
playing,
|
|
volume,
|
|
currentTime,
|
|
loadVideo,
|
|
togglePictureInPicture,
|
|
}
|
|
})
|
|
```
|
|
|
|
**Note:** Don't return non-serializable DOM refs like `videoElement` - they're internal implementation details.
|
|
|
|
## SSR Considerations
|
|
|
|
### Option Stores with hydrate()
|
|
|
|
Define a `hydrate()` function to handle client-side hydration:
|
|
|
|
```ts
|
|
import { defineStore } from 'pinia'
|
|
import { useLocalStorage } from '@vueuse/core'
|
|
|
|
export const useAuthStore = defineStore('auth', {
|
|
state: () => ({
|
|
user: useLocalStorage('pinia/auth/login', 'bob'),
|
|
}),
|
|
|
|
hydrate(state, initialState) {
|
|
// Ignore server state, read from browser
|
|
state.user = useLocalStorage('pinia/auth/login', 'bob')
|
|
},
|
|
})
|
|
```
|
|
|
|
### Setup Stores with skipHydrate()
|
|
|
|
Mark state that shouldn't hydrate from server:
|
|
|
|
```ts
|
|
import { defineStore, skipHydrate } from 'pinia'
|
|
import { useEyeDropper, useLocalStorage } from '@vueuse/core'
|
|
|
|
export const useColorStore = defineStore('colors', () => {
|
|
const { isSupported, open, sRGBHex } = useEyeDropper()
|
|
const lastColor = useLocalStorage('lastColor', sRGBHex)
|
|
|
|
return {
|
|
// Skip hydration for client-only state
|
|
lastColor: skipHydrate(lastColor),
|
|
open, // Function - no hydration needed
|
|
isSupported, // Boolean - not reactive
|
|
}
|
|
})
|
|
```
|
|
|
|
`skipHydrate()` only applies to state properties (refs), not functions or non-reactive values.
|
|
|
|
<!--
|
|
Source references:
|
|
- https://pinia.vuejs.org/cookbook/composables.html
|
|
-->
|