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-lift-state.md
1---2title: Lift State into Provider Components3impact: HIGH4impactDescription: enables state sharing outside component boundaries5tags: composition, state, context, providers6---78## Lift State into Provider Components910Move state management into dedicated provider components. This allows sibling11components outside the main UI to access and modify state without prop drilling12or awkward refs.1314**Incorrect (state trapped inside component):**1516```tsx17function ForwardMessageComposer() {18const [state, setState] = useState(initialState)19const forwardMessage = useForwardMessage()2021return (22<Composer.Frame>23<Composer.Input />24<Composer.Footer />25</Composer.Frame>26)27}2829// Problem: How does this button access composer state?30function ForwardMessageDialog() {31return (32<Dialog>33<ForwardMessageComposer />34<MessagePreview /> {/* Needs composer state */}35<DialogActions>36<CancelButton />37<ForwardButton /> {/* Needs to call submit */}38</DialogActions>39</Dialog>40)41}42```4344**Incorrect (useEffect to sync state up):**4546```tsx47function ForwardMessageDialog() {48const [input, setInput] = useState('')49return (50<Dialog>51<ForwardMessageComposer onInputChange={setInput} />52<MessagePreview input={input} />53</Dialog>54)55}5657function ForwardMessageComposer({ onInputChange }) {58const [state, setState] = useState(initialState)59useEffect(() => {60onInputChange(state.input) // Sync on every change 😬61}, [state.input])62}63```6465**Incorrect (reading state from ref on submit):**6667```tsx68function ForwardMessageDialog() {69const stateRef = useRef(null)70return (71<Dialog>72<ForwardMessageComposer stateRef={stateRef} />73<ForwardButton onPress={() => submit(stateRef.current)} />74</Dialog>75)76}77```7879**Correct (state lifted to provider):**8081```tsx82function ForwardMessageProvider({ children }: { children: React.ReactNode }) {83const [state, setState] = useState(initialState)84const forwardMessage = useForwardMessage()85const inputRef = useRef(null)8687return (88<Composer.Provider89state={state}90actions={{ update: setState, submit: forwardMessage }}91meta={{ inputRef }}92>93{children}94</Composer.Provider>95)96}9798function ForwardMessageDialog() {99return (100<ForwardMessageProvider>101<Dialog>102<ForwardMessageComposer />103<MessagePreview /> {/* Custom components can access state and actions */}104<DialogActions>105<CancelButton />106<ForwardButton /> {/* Custom components can access state and actions */}107</DialogActions>108</Dialog>109</ForwardMessageProvider>110)111}112113function ForwardButton() {114const { actions } = use(Composer.Context)115return <Button onPress={actions.submit}>Forward</Button>116}117```118119The ForwardButton lives outside the Composer.Frame but still has access to the120submit action because it's within the provider. Even though it's a one-off121component, it can still access the composer's state and actions from outside the122UI itself.123124**Key insight:** Components that need shared state don't have to be visually125nested inside each other—they just need to be within the same provider.126