Lift state into a provider
Siblings outside the UI read state from a provider, not effects or refs.
React docsreact.dev
Context lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props.
From vercel-composition-patterns, rule lift-state.
A preview outside the frame
Press Type, or type in either composer. Flashes are renders.
useEffect syncs upBad
useEffect(() => onInputChange(input), [input])State in a providerGood
const { state } = use(Composer.Context)function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState(initialState)
const forwardMessage = useForwardMessage()
const inputRef = useRef(null)
return (
<Composer.Provider
state={state}
actions={{ update: setState, submit: forwardMessage }}
meta={{ inputRef }}
>
{children}
</Composer.Provider>
)
}
function ForwardMessageDialog() {
return (
<ForwardMessageProvider>
<Dialog>
<ForwardMessageComposer />
<MessagePreview />
<DialogActions>
<CancelButton />
<ForwardButton />
</DialogActions>
</Dialog>
</ForwardMessageProvider>
)
}
function ForwardButton() {
const { actions } = use(Composer.Context)
return <Button onPress={actions.submit}>Forward</Button>
}
Submit that reads a ref
Clear the text, then press Forward in each.
stateRef on submitBad
MessagePreviewcan't see state in a ref
onPress={() => submit(stateRef.current)}ProviderGood
MessagePreviewdisabled={!state.input.trim()}Why it matters, pros and cons
Why it matters
State that siblings need belongs above them. The provider boundary matters, not visual nesting.
Pros
- Preview and button read live state.
- No sync effect, no stale frame, no extra render.
- No imperative ref reads at submit time.
Cons
- Every consumer re-renders per keystroke; split contexts if heavy.
- State only one component uses should stay local.
- A provider per dialog is more ceremony.