Context as a contract

Every provider fills { state, actions, meta }. Any part fits any.

From vercel-composition-patterns, rule context-interface.

Plug parts into providers

Press Type-check to try every part under every provider.

Ad-hoc shapesBad

– of 12 compile
const { input, updateInput } = useChannelComposer()
One interfaceGood

– of 12 compile
const { state, actions } = use(ComposerContext)
interface ComposerState {
  input: string
  attachments: Attachment[]
  isSubmitting: boolean
}

interface ComposerActions {
  update: (updater: (state: ComposerState) => ComposerState) => void
  submit: () => void
}

interface ComposerMeta {
  inputRef: React.RefObject<TextInput>
}

interface ComposerContextValue {
  state: ComposerState
  actions: ComposerActions
  meta: ComposerMeta
}

const ComposerContext = createContext<ComposerContextValue | null>(null)

function ComposerInput() {
  const {
    state,
    actions: { update },
    meta,
  } = use(ComposerContext)

  // This component works with ANY provider that implements the interface
  return (
    <TextInput
      ref={meta.inputRef}
      value={state.input}
      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
    />
  )
}

meta carries the refs

Use the toolbar outside the composer frame.

Ref local to InputBad
<DialogToolbar />

const inputRef = useRef(null) // inside Input
meta.inputRefGood
<DialogToolbar />

meta.inputRef.current?.focus()
function DialogToolbar() {
  const { actions, meta } = use(ComposerContext)
  return (
    <>
      <Button onPress={() => meta.inputRef.current?.focus()}>Focus</Button>
      <Button onPress={() => actions.update((s) => ({ ...s, input: s.input + '@ada ' }))}>
        Mention
      </Button>
    </>
  )
}

Why it matters, pros and cons

Why it matters

One fixed shape turns providers into plug-ins. Parts code against the contract, never a hook.

Pros

  • Any part works under any provider.
  • state, actions, meta say where each thing lives.
  • Refs travel without forwardRef chains.

Cons

  • A generic update(fn) is looser than named actions.
  • Every provider must fill the whole contract.
  • meta can turn into a junk drawer.