Null is not an ObjectHome Link
Prop Collections and Getters

Prop Collections and Getters

July 8, 2021 3 min read

When building a custom component, you might encounter a scenario where certain elements have identicle attributes or props applied to them.

Here's the Day&Night component we'll use as an example:

We want this component to have the following props and attributes:

  • aria-pressed
  • onClick
  • on
function useToggle() {
  const [on, setOn] = React.useState(false);
  const toggle = () => setOn(!on);

  return {
    on,
    toggle,
  };
}

const PropCollectionsAndGetters = () => {
  const { toggle, on } = useToggle();
  return (
    <>
      <Animation on={on}>
<Button on={on} onClick={toggle} aria-pressed={on} aria-label="toggle-button" />
</Animation>
<Switch on={on} onClick={toggle} aria-pressed={on} />
</> ); };

We have common props for both the Button and Switch components. Both components require these props by default either for functionality or accessibility.

We can create a collection of props to share with both components, ensuring none of the required props are accidentally omitted.

Prop Collections

For common use cases, we can create prop collections containing all the required or default props for a component:

function useToggle() {
  const [on, setOn] = React.useState(false);
  const toggle = () => setOn(!on);

  return {
    on,
    toggle,
toggleProps: {
on,
onClick: toggle,
"aria-pressed": on,
},
}; }

This enables us to add all default props to components more easily:

const PropCollectionsAndGetters = () => {
const { on, toggleProps } = useToggle();
return ( <Wrapper> <Animation on={on}>
<Button {...toggleProps} aria-label="toggle-button" />
</Animation>
<Switch {...toggleProps} />
</Wrapper> ); };

Prop Getters

Prop collections work well for a specific requirement, but they aren't very flexible.

We may want to pass in our own click handler for custom scenarios where an event is emitted onClick.

But the example below breaks the toggle functionality, because our own click handler is overriding the toggleProps click handler we are also passing in.

const PropCollectionsAndGetters = () => {
  const { on, toggleProps } = useToggle();
  return (
    <Wrapper>
      <Animation on={on}>
        <Button {...toggleProps} aria-label="toggle-button" />
      </Animation>
<Switch {...toggleProps} onClick={() => console.log('hello')} />
</Wrapper> ); };

The solution is to compose the default props we want to apply to the Button and Switch components with any custom props we also want to add.

getToggleProps is a utility function that accepts any custom props or attributes we want to apply to an element or component and composes them with the default props.

function useToggle() {
  const [on, setOn] = React.useState(false);
  const toggle = () => setOn(!on);

const callAll = (...fns) => (...args) => fns.forEach(fn => fn?.(...args))

function getToggleProps({ onClick, ...props } = {}) {
return {
"aria-pressed": on,
onClick: callAll(onClick, toggle),
on,
...props,
};
}
return { on, toggle, getToggleProps, }; }

getToggleProps returns an object of the composed custom and default props which we can spread to our components.

const PropCollectionsAndGetters = () => {
  const { on, getToggleProps } = useToggle();
  return (
    <Wrapper>
      <Animation on={on}>
        <Button
{...getToggleProps({
"aria-label": "toggle-button",
onClick: () => console.log("hello"),
})}
/> </Animation>
<Switch {...getToggleProps()} />
</Wrapper> ); };

Further Reading