--- title: Use shallowRef Pattern for External State Libraries impact: MEDIUM impactDescription: External state systems (Immer, XState, Redux) should use shallowRef to avoid double-wrapping in proxies type: efficiency tags: [vue3, reactivity, shallowRef, external-state, immer, xstate, integration] --- # Use shallowRef Pattern for External State Libraries **Impact: MEDIUM** - When integrating Vue with external state management libraries (Immer, XState, Redux, MobX), use `shallowRef()` to hold the external state. This prevents Vue from deep-wrapping the external state in proxies, which can cause conflicts and performance issues. The pattern: hold external state in a `shallowRef`, and replace `.value` entirely when the external system updates. This gives Vue reactivity while letting the external library manage state internals. ## Task Checklist - [ ] Use `shallowRef()` to hold external library state - [ ] Replace `.value` entirely when external state changes (don't mutate) - [ ] Integrate update functions that produce new state objects - [ ] Consider this pattern for immutable data structures **Integrating with Immer:** ```javascript import { produce } from 'immer'; import { shallowRef } from 'vue'; export function useImmer(baseState) { const state = shallowRef(baseState); function update(updater) { // Immer produces a new immutable state // Replace shallowRef value entirely to trigger reactivity state.value = produce(state.value, updater); } return [state, update]; } // Usage const [todos, updateTodos] = useImmer([{ id: 1, text: 'Learn Vue', done: false }]); // Update with Immer's mutable API (produces immutable result) updateTodos((draft) => { draft[0].done = true; draft.push({ id: 2, text: 'Use Immer', done: false }); }); ``` **Integrating with XState:** ```javascript import { createMachine, interpret } from 'xstate'; import { shallowRef, onUnmounted } from 'vue'; export function useMachine(options) { const machine = createMachine(options); const state = shallowRef(machine.initialState); const service = interpret(machine) .onTransition((newState) => { // Replace state entirely on each transition state.value = newState; }) .start(); const send = (event) => service.send(event); onUnmounted(() => service.stop()); return { state, send }; } // Usage const { state, send } = useMachine({ id: 'toggle', initial: 'inactive', states: { inactive: { on: { TOGGLE: 'active' } }, active: { on: { TOGGLE: 'inactive' } }, }, }); // In template: state.value.matches('active') send('TOGGLE'); ``` **Integrating with Redux-style stores:** ```javascript import { shallowRef, readonly } from 'vue'; export function createStore(reducer, initialState) { const state = shallowRef(initialState); function dispatch(action) { state.value = reducer(state.value, action); } function getState() { return state.value; } return { state: readonly(state), // Prevent direct mutations dispatch, getState, }; } // Usage const store = createStore( (state, action) => { switch (action.type) { case 'INCREMENT': return { ...state, count: state.count + 1 }; default: return state; } }, { count: 0 }, ); store.dispatch({ type: 'INCREMENT' }); console.log(store.state.value.count); // 1 ``` **Why NOT use ref() for external state:** ```javascript import { ref } from 'vue'; import { produce } from 'immer'; // WRONG: ref() deep-wraps the state const state = ref({ nested: { value: 1 } }); // This creates double-proxying: // 1. Vue wraps state in Proxy // 2. External library may also wrap/expect raw objects // 3. Causes identity issues and potential conflicts // WRONG: Mutating ref with Immer state.value = produce(state.value, (draft) => { draft.nested.value = 2; }); // Vue's deep proxy on state.value may interfere with Immer's proxies ``` **Correct pattern with shallowRef:** ```javascript import { shallowRef } from 'vue'; // CORRECT: shallowRef only tracks .value replacement const state = shallowRef({ nested: { value: 1 } }); // External library works with raw objects inside state.value = produce(state.value, (draft) => { draft.nested.value = 2; }); // Clean separation: Vue tracks outer ref, library manages inner state ``` ## Reference - [Vue.js Reactivity in Depth - Integration with External State](https://vuejs.org/guide/extras/reactivity-in-depth.html#integration-with-external-state-systems) - [Vue.js shallowRef API](https://vuejs.org/api/reactivity-advanced.html#shallowref) - [Immer Documentation](https://immerjs.github.io/immer/) - [XState Documentation](https://xstate.js.org/)