Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Apply React composition patterns to build flexible, maintainable components without boolean prop sprawl
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/state-decouple-implementation.md
1---2title: Decouple State Management from UI3impact: MEDIUM4impactDescription: enables swapping state implementations without changing UI5tags: composition, state, architecture6---78## Decouple State Management from UI910The provider component should be the only place that knows how state is managed.11UI components consume the context interface—they don't know if state comes from12useState, Zustand, or a server sync.1314**Incorrect (UI coupled to state implementation):**1516```tsx17function ChannelComposer({ channelId }: { channelId: string }) {18// UI component knows about global state implementation19const state = useGlobalChannelState(channelId)20const { submit, updateInput } = useChannelSync(channelId)2122return (23<Composer.Frame>24<Composer.Input25value={state.input}26onChange={(text) => sync.updateInput(text)}27/>28<Composer.Submit onPress={() => sync.submit()} />29</Composer.Frame>30)31}32```3334**Correct (state management isolated in provider):**3536```tsx37// Provider handles all state management details38function ChannelProvider({39channelId,40children,41}: {42channelId: string43children: React.ReactNode44}) {45const { state, update, submit } = useGlobalChannel(channelId)46const inputRef = useRef(null)4748return (49<Composer.Provider50state={state}51actions={{ update, submit }}52meta={{ inputRef }}53>54{children}55</Composer.Provider>56)57}5859// UI component only knows about the context interface60function ChannelComposer() {61return (62<Composer.Frame>63<Composer.Header />64<Composer.Input />65<Composer.Footer>66<Composer.Submit />67</Composer.Footer>68</Composer.Frame>69)70}7172// Usage73function Channel({ channelId }: { channelId: string }) {74return (75<ChannelProvider channelId={channelId}>76<ChannelComposer />77</ChannelProvider>78)79}80```8182**Different providers, same UI:**8384```tsx85// Local state for ephemeral forms86function ForwardMessageProvider({ children }) {87const [state, setState] = useState(initialState)88const forwardMessage = useForwardMessage()8990return (91<Composer.Provider92state={state}93actions={{ update: setState, submit: forwardMessage }}94>95{children}96</Composer.Provider>97)98}99100// Global synced state for channels101function ChannelProvider({ channelId, children }) {102const { state, update, submit } = useGlobalChannel(channelId)103104return (105<Composer.Provider state={state} actions={{ update, submit }}>106{children}107</Composer.Provider>108)109}110```111112The same `Composer.Input` component works with both providers because it only113depends on the context interface, not the implementation.114