HomeAbout Me

Advanced React Patterns 5: Prop Collections and Getters

By Daniel Nguyen
Published in React JS
June 25, 2025
2 min read
Advanced React Patterns 5: Prop Collections and Getters

Prop Collections and Getters

**One liner:** The Prop Collections and Getters Pattern allows your hook to support common use cases for UI elements people build with your hook.

In typical UI components, you need to take accessibility into account. For a button functioning as a toggle, it should have the aria-checked attribute set to true or false if it’s toggled on or off. In addition to remembering that, people need to remember to also add the onClick handler to call toggle.

Lots of the reusable/flexible components and hooks that we’ll create have some common use-cases and it’d be cool if we could make it easier to use our components and hooks the right way without requiring people to wire things up for common use cases.

Here’s a quick example:

function useCounter() {
const [count, setCount] = useState(initialCount)
const increment = () => setCount(c => c + 1)
return {
count,
increment,
counterButtonProps: { children: count, onClick: increment },
} // <-- this is the prop collection
}
function App() {
const counter = useCounter()
return <button {...counter.counterButtonProps} />
}

🦉 Note that we’re moving from a collection of related components (compound components) to a hook for the upcoming patterns. We’ll bring back a Toggle component that uses the hook later, but often it’s useful to drop down to a lower level of abstraction to give consumers more power and then build on top of that. And you can definitely combine the patterns (read my old post on the subject demonstrating how to do this with class components: Mixing Component Patterns).

Real World Projects that use this pattern:

  • downshift (uses prop getters)
  • conform’s getFormProps (prop getters)

Prop Collections

🧝‍♂️ I’ve removed the work we did with the Slot pattern and we’re also going to be focusing the next few exercises on the hook. So we’ve simplified things a little bit to give us a better focus on the hook.

👨‍💼 In our simple example, this isn’t too much for folks to remember, but in more complex components, the list of props that need to be applied to elements can be extensive, so it can be a good idea to take the common use cases for our hook and/or components and make objects of props that people can simply spread across the UI they render.

In this exercise you need to create a togglerProps object that has all the props people would typically need applied to a toggle button.

Prop Getters

👨‍💼 Uh oh! Someone wants to use our togglerProps object, but they need to apply their own onClick handler! Try doing that by updating the App component to this:

function App() {
const { on, togglerProps } = useToggle()
return (
<div>
<Switch on={on} {...togglerProps} />
<hr />
<button
aria-label="custom-button"
{...togglerProps}
onClick={() => console.info('onButtonClick')}
>
{on ? 'on' : 'off'}
</button>
</div>
)
}

I want both the toggle to work as well as the log. Does that work? Why not? Can you change it to make it work?

What if we change the API slightly so that instead of having an object of props, we call a function to get the props. Then we can pass that function the props we want applied and that function will be responsible for composing the props together.

Let’s try that. Our

file has been updated to use a new API we’re responsible for creating. See if you can make that API work.

🦺 The types for the argument to the getTogglerProps component might be a bit tricky, so here’s a little tip: you can get the onClick prop from: React.ComponentProps<'button'>['onClick'].

app.tsx

import { Switch } from '#shared/switch.tsx'
import { useToggle } from './toggle.tsx'
export function App() {
const { on, getTogglerProps } = useToggle()
return (
<div>
<Switch {...getTogglerProps({ on })} />
<hr />
<button
{...getTogglerProps({
'aria-label': 'custom-button',
onClick: () => console.info('onButtonClick'),
id: 'custom-button-id',
})}
>
{on ? 'on' : 'off'}
</button>
</div>
)
}

toggle.tsx

import { useState } from 'react'
function callAll<Args extends Array<unknown>>(
...fns: Array<((...args: Args) => unknown) | undefined>
) {
return (...args: Args) => fns.forEach(fn => fn?.(...args))
}
export function useToggle() {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
function getTogglerProps<Props>({
onClick,
...props
}: {
onClick?: React.ComponentProps<'button'>['onClick']
} & Props) {
return {
'aria-checked': on,
onClick: callAll(onClick, toggle),
...props,
}
}
return {
on,
toggle,
getTogglerProps,
}
}

Tags

#React

Share

Previous Article
Advanced React Patterns 4: Slots

Table Of Contents

1
Prop Collections and Getters
2
Prop Collections
3
Prop Getters

Related Posts

React Testing 8: Testing custom hook
September 09, 2025
1 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media