When creating a component in React, we can use props like this:
export const Checkbox = () => {
// ...logic here
return <input type="checkbox" checked={isChecked} disabled={isDisabled} value={value} onChange={onChange} …>
}
This works, but there are a couple of downsides.
- If your component has a lot of props, and a lot of logic, it can make a large file that is hard for other developers to read.
- You may want to reuse that logic somewhere else, which would require a refactor.
A pattern that solves both of those problems is prop hooks. All the logic and props are abstracted to a hook that returns component props.
// hooks/useCheckbox.ts
function useCheckbox() {
// logic here
const checkboxProps = {
type: 'checkbox',
checked: state?.isChecked,
disabled: state?.isDisabled,
value: state?.value,
onChange: props.onChange
}
return { checkboxProps }
}
// components/Checkbox.tsx
export const Checkbox = () => {
const { checkboxProps } = useCheckbox(props)
return <input {…checkboxProps} />
}
In the example above, useCheckbox() returns props like checked, value, onChange, disabled, and aria attributes. It’s everything you need to display a checkbox.
So what’s the difference? Why abstract all the props to a useCheckbox hook? There’s two reasons.
First, it separates your logic from your display. You can easily see the code that handles the display, which is the component. All the logic is handled in your hook.
Second, you can reuse your useCheckbox() hook easily. With all the props you need abstracted to your hook, you can create a checkbox somewhere else in your application easily.
// components/DifferentCheckbox.tsx
export const DifferentCheckbox = () => {
const { checkboxProps } = useCheckbox(props)
return <input {…checkboxProps} />
}
This also allows you to share code between applications if you make all your logic a library.