--- 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() 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.