🧰 Prop Collections and Getters in React: Building Flexible Hooks Without Sacrificing Usability
React hooks are amazing for encapsulating stateful logic, but when you’re building reusable hooks—especially for component libraries—you often need to do a bit more than just return state and state setters.
This is where the Prop Collections and Prop Getters patterns shine. They help abstract away common event handling, accessibility concerns, and interaction logic—making it easier for consumers of your hook to use them correctly with minimal configuration.
Let’s explore how this works. 👇
Let’s say you’re building a simple toggle button using a custom useToggle
hook. You’d typically expect users to do this:
function App() {const { on, toggle } = useToggle()return <button aria-pressed={on} onClick={toggle}>{on ? 'On' : 'Off'}</button>}
Not too bad, right?
But in more complex scenarios, users might need to remember:
aria-pressed
for accessibilityonClick
to trigger toggle
This can become error-prone. Wouldn’t it be great if your hook could suggest the right props or even generate them for the user?
A prop collection is a bundle of props your hook returns that users can spread onto an element.
function useCounter(initialCount = 0) {const [count, setCount] = React.useState(initialCount)const increment = () => setCount(c => c + 1)return {count,increment,counterButtonProps: {children: count,onClick: increment,},}}function App() {const counter = useCounter()return <button {...counter.counterButtonProps} />}
💡 Prop collections make it easy to use your hook for typical cases, without thinking about implementation details.
What happens if someone needs to customize a prop—say, they want to add their own onClick
?
<button{...togglerProps}onClick={() => console.log('Custom click')}/>
Uh oh… Your onClick
just got overwritten! The toggle logic no longer runs.
Prop Getters solve this by turning the prop object into a function.
<button {...togglerProps} />
<button {...getTogglerProps({ onClick: () => console.log('custom') })} />
Now the getter function can merge or compose the incoming props with its internal behavior—so both toggle()
and your custom console.log()
work together.
useToggle
Here’s how it might look:
function useToggle() {const [on, setOn] = React.useState(false)const toggle = () => setOn(prev => !prev)const getTogglerProps = ({onClick,...props}: React.ComponentProps<'button'> = {}) => {return {'aria-pressed': on,onClick: (e: React.MouseEvent) => {onClick?.(e)toggle()},...props,}}return { on, toggle, getTogglerProps }}
This ensures the toggle functionality always runs, even when developers pass their own onClick
.
Benefit | Description |
---|---|
Composability | Users can layer their own props on top of internal ones |
Accessibility | Ensure the correct ARIA attributes are always included |
Encapsulation | Logic stays inside your hook, UI stays flexible |
Reusability | Build components and hooks once, use them everywhere |
Here’s how you use it:
function App() {const { on, getTogglerProps } = useToggle()return (<div><button {...getTogglerProps()}>{on ? 'ON' : 'OFF'}</button><button{...getTogglerProps({onClick: () => console.log('Custom click'),'aria-label': 'custom-button',})}>{on ? 'Custom On' : 'Custom Off'}</button></div>)}
Now both buttons work flawlessly—and even your accessibility tooling will thank you. 🧼
You can use prop getters inside a component like ToggleButton
, then hide all the wiring from the outside world:
function ToggleButton() {const { getTogglerProps } = useToggle()return <button {...getTogglerProps()} />}
Or allow consumers to opt-in to either:
<ToggleButton />
)useToggle()
)This hybrid approach gives you the best of both worlds: power + simplicity.
The Prop Collections and Getters pattern is a powerful addition to your React toolkit—especially when building hooks for reusable UI logic.
It helps you:
Whether you’re creating a design system, open-source library, or an internal component platform, adopting this pattern can make your APIs both easy to use and hard to misuse.
Quick Links
Legal Stuff
Social Media