Controlled and uncontrolled

Uncontrolled

Pass initialValue and the component keeps its own state. Use onChange only when you need to know what changed.

import { Tabs } from '@bolio-ui/core'

<Tabs initialValue="billing" onChange={(value) => console.log(value)}>
  <Tabs.Item label="Billing" value="billing">…</Tabs.Item>
  <Tabs.Item label="Profile" value="profile">…</Tabs.Item>
</Tabs>

The initial prop is read once, on the first render. Changing it later does nothing.

Controlled

Pass value and keep the state yourself. Update it inside onChange, so the component always shows what your state says.

import { useState } from 'react'
import { Slider } from '@bolio-ui/core'

function Volume() {
  const [volume, setVolume] = useState(20)

  return (
    <>
      <Slider aria-label="Volume" value={volume} onChange={setVolume} />
      <button onClick={() => setVolume(0)}>Mute</button>
    </>
  )
}

Use this mode when something else needs to change the value too, such as a reset button, a URL parameter or a saved draft.

Prop names

The pair is always the same: a prop that follows your state and an initial* prop for the starting value.

ControlledUncontrolledComponents
valueinitialValueInput, Textarea, Select, Combobox, Tabs, Slider, Rating, NumberInput, SegmentedControl, TagsInput, PinInput, DatePicker
checkedinitialCheckedToggle, Checkbox
pageinitialPagePagination

onChange has the same name everywhere, but its argument changes with the component. Tabs, Slider and Pagination give the new value directly. Toggle gives an event, so read event.target.checked.

Pick one mode

Decide at the start and keep it for the whole life of the component. Passing value on some renders and leaving it out on others makes the field jump between your state and its own.

Start a controlled field with a real value, such as an empty string for a text field, never undefined.

In your own components

useUncontrolled gives your component the same behavior with one call. It returns the current value, a setter and a flag that tells which mode is active.