bniwredyc
bniwredyc

Reputation: 8829

Universal solution for data attributes in React

I have some duplicating code across my project that looks like this:

function MyComp({prop1, prop2, ...restProps}) {
    
    // ...

    const dataAttributes = Object.keys(restProps).reduce((acc, key) => {
      if (key.startsWith('data-')) {
        acc[key] = restProps[key];
      }

      return acc;
    }, {});

    return (
        <div {...dataAttributes}>
            {/* ... */}
        </div>
    );
}

I'm thinking about automation of this behaviour (pass props that start with data- to the root element of the component if it's an html element).

Any thoughts?

Upvotes: 4

Views: 2993

Answers (3)

buzatto
buzatto

Reputation: 10382

you could create a HOC function, where you can pass the remaining props to Component:

const withDataAttribute = (Component) => (props) => {
  let passThroughProps = { ...props }
  const dataAttributes = Object.keys(passThroughProps).reduce((acc, key) => {
    if (key.startsWith('data-')) {
      acc[key] = passThroughProps[key];
      const { [key]: any, ...rest } = passThroughProps
      passThroughProps = rest
    }

    return acc;
  }, {});

  return (
    <div {...dataAttributes}>
      <Component {...passThroughProps} />
    </div>
  );
}

Upvotes: 1

Maybe you just can create a custom component for the html tags you use, like RichDiv, RichSpan... It would be like:

const getDataAttributes = (inputProps = {}) => Object.keys(inputProps).reduce((acc, key) => {
      if (key.startsWith('data-')) {
        acc[key] = inputProps[key];
      }

      return acc;
    }, {});

function RichDiv({children, className, ...restProps}) {
    const dataAttributes = getDataAttributes(restProps);

    return (
        <div {...dataAttributes} className={className}>
            {children}
        </div>
    );
}

function RichSpan({children, className, ...restProps}) {
    const dataAttributes = getDataAttributes(restProps);

    return (
        <span {...dataAttributes} className={className}>
            {children}
        </span>
    );
}

You can add any other whitelisted prop like I did with className to the custom components or even improve the props filter function you made to make it return both data- attributes and other HTML common attributes. Then, you just import and use your RichDiv instead of vanilla div and so.

Upvotes: 2

Terbiy
Terbiy

Reputation: 690

The first and not very complex step might be the extraction of reducing part to a separate utility function like this:

export function extractDataAttributes(props) {
    return Object.keys(props).reduce((acc, key) => {
        if (key.startsWith("data-")) {
            acc[key] = props[key];
        }

        return acc;
    }, {});
}

Then you can use it in the following shorter way:

function MyComp({prop1, prop2, ...restProps}) {
    // ...
  
    return (
        <div {...extractDataAttributes(restProps)}>
            {/* ... */}
        </div>
    );
}

By doing so, you'll also explicitly mark that the resulting markup has data-attributes. On the other hand, there will be no unnecessary details.

If you find this solution insufficient at some point, it might be a good idea to use HOC looking inside its children and updating them. However, this approach might lower the explicitness of your code.

Upvotes: 1

Related Questions