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>
114 lines
2.6 KiB
Markdown
114 lines
2.6 KiB
Markdown
---
|
|
title: Decouple State Management from UI
|
|
impact: MEDIUM
|
|
impactDescription: enables swapping state implementations without changing UI
|
|
tags: composition, state, architecture
|
|
---
|
|
|
|
## Decouple State Management from UI
|
|
|
|
The provider component should be the only place that knows how state is managed.
|
|
UI components consume the context interface—they don't know if state comes from
|
|
useState, Zustand, or a server sync.
|
|
|
|
**Incorrect (UI coupled to state implementation):**
|
|
|
|
```tsx
|
|
function ChannelComposer({ channelId }: { channelId: string }) {
|
|
// UI component knows about global state implementation
|
|
const state = useGlobalChannelState(channelId)
|
|
const { submit, updateInput } = useChannelSync(channelId)
|
|
|
|
return (
|
|
<Composer.Frame>
|
|
<Composer.Input
|
|
value={state.input}
|
|
onChange={(text) => sync.updateInput(text)}
|
|
/>
|
|
<Composer.Submit onPress={() => sync.submit()} />
|
|
</Composer.Frame>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correct (state management isolated in provider):**
|
|
|
|
```tsx
|
|
// Provider handles all state management details
|
|
function ChannelProvider({
|
|
channelId,
|
|
children,
|
|
}: {
|
|
channelId: string
|
|
children: React.ReactNode
|
|
}) {
|
|
const { state, update, submit } = useGlobalChannel(channelId)
|
|
const inputRef = useRef(null)
|
|
|
|
return (
|
|
<Composer.Provider
|
|
state={state}
|
|
actions={{ update, submit }}
|
|
meta={{ inputRef }}
|
|
>
|
|
{children}
|
|
</Composer.Provider>
|
|
)
|
|
}
|
|
|
|
// UI component only knows about the context interface
|
|
function ChannelComposer() {
|
|
return (
|
|
<Composer.Frame>
|
|
<Composer.Header />
|
|
<Composer.Input />
|
|
<Composer.Footer>
|
|
<Composer.Submit />
|
|
</Composer.Footer>
|
|
</Composer.Frame>
|
|
)
|
|
}
|
|
|
|
// Usage
|
|
function Channel({ channelId }: { channelId: string }) {
|
|
return (
|
|
<ChannelProvider channelId={channelId}>
|
|
<ChannelComposer />
|
|
</ChannelProvider>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Different providers, same UI:**
|
|
|
|
```tsx
|
|
// Local state for ephemeral forms
|
|
function ForwardMessageProvider({ children }) {
|
|
const [state, setState] = useState(initialState)
|
|
const forwardMessage = useForwardMessage()
|
|
|
|
return (
|
|
<Composer.Provider
|
|
state={state}
|
|
actions={{ update: setState, submit: forwardMessage }}
|
|
>
|
|
{children}
|
|
</Composer.Provider>
|
|
)
|
|
}
|
|
|
|
// Global synced state for channels
|
|
function ChannelProvider({ channelId, children }) {
|
|
const { state, update, submit } = useGlobalChannel(channelId)
|
|
|
|
return (
|
|
<Composer.Provider state={state} actions={{ update, submit }}>
|
|
{children}
|
|
</Composer.Provider>
|
|
)
|
|
}
|
|
```
|
|
|
|
The same `Composer.Input` component works with both providers because it only
|
|
depends on the context interface, not the implementation.
|