Zustand without the spread
Nested immutable updates quickly become a wall of spread operators:
setCity: (city) => set((state) => ({ ...state, user: { ...state.user, address: { ...state.user.address, city, }, }, }))An optic packages the read and immutable-update path as a reusable value.
import * as Optic from '@stopcock/fp/optic'
const city = Optic.optic<State>() .prop('user') .prop('address') .prop('city') .valueThe Zustand action becomes:
setCity: (value) => set((state) => Optic.set(city, value)(state))A complete store
Section titled “A complete store”import { create } from 'zustand'import * as Optic from '@stopcock/fp/optic'
const city = Optic.optic<State>().prop('user').prop('address').prop('city').valueconst theme = Optic.optic<State>().prop('ui').prop('theme').valueconst sidebar = Optic.optic<State>().prop('ui').prop('sidebarOpen').valueconst discount = Optic.optic<State>().prop('cart').prop('discount').value
const useStore = create<State & Actions>((set) => ({ user: { name: 'Alice', address: { street: '123 Main St', city: 'Portland', postcode: '97201', }, }, cart: { items: [], discount: null }, ui: { sidebarOpen: false, theme: 'light' },
setCity: (value) => set((state) => Optic.set(city, value)(state)), setTheme: (value) => set((state) => Optic.set(theme, value)(state)), applyDiscount: (value) => set((state) => Optic.set(discount, value)(state)), toggleSidebar: () => set((state) => Optic.modify(sidebar, (open) => !open)(state)),}))The optic is defined once, inferred from State, and independently testable.
Conditional array updates
Section titled “Conditional array updates”Traversals focus on zero or more values:
const outOfStock = Optic.filtered<CartItem>((item) => item.stock === 0)
const updatedItems = Optic.modify( outOfStock, (item) => ({ ...item, disabled: true }),)(items)Compose the traversal with a lens to update items directly inside state:
const cartItems = Optic.optic<State>().prop('cart').prop('items').valueconst unavailableItems = Optic.compose(cartItems, outOfStock)
const markOutOfStock = () => set((state) => Optic.modify( unavailableItems, (item) => ({ ...item, disabled: true }), )(state), )Partial focus
Section titled “Partial focus”index and find produce Optional optics. Reads return Option; writes
leave the source unchanged when no focus exists.
const selected = Optic.find<CartItem>((item) => item.id === selectedId)const current = Optic.preview(selected)(items)const renamed = Optic.modify(selected, (item) => ({ ...item, name: item.name.trim(),}))(items)When to use optics
Section titled “When to use optics”Flat state rarely needs them. Optics pay off when paths are nested, shared by
several actions, partial, or multi-focus. The 2.0 module also includes prisms,
isomorphisms, getters, folds, setters, keyed At optics, composition, and lens
law checks.