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.
Passing Data Deeply with ContextRead docs ↗

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
MessagePreview
can't see state in a ref

onPress={() => submit(stateRef.current)}
ProviderGood
MessagePreview

disabled={!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.