React: Stop doing data-*, aria-*, start using dataSet

Created on 14 Mar 2014  路  27Comments  路  Source: facebook/react

The DOM already exposes data-* as dataset but it's doing transformation from hyphenated to camelCase. From MDN:

<div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth>John Doe
</div>

var el = document.querySelector('#user');

// el.id == 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'johndoe'
// el.dataset.dateOfBirth === ''

el.dataset.dateOfBirth = '1960-10-03'; // set the DOB.

// 'someDataAttr' in el.dataset === false

el.dataset.someDataAttr = 'mydata';
// 'someDataAttr' in el.dataset === true

We should just start supporting dataSet (because camelCase). This will allow a couple things:

  • easier reasoning about data attributes (Object.keys(this.props.dataSet))
  • easier merging (<div dataSet={merge(this.props.dataSet, {extra: 'value', override: 'value'})} />)
  • easier (potentially faster?) updates (just modify node.dataset)

We'll want to do the reverse of what the DOM is doing. eg <div dataSet={{dateOfBirth: 'val', foo: 'bar'}} /> becomes <div data-date-of-birth="val" data-foo="bar"></div>.

To the best of my knowledge, aria-* doesn't have a corresponding API, but we should make it work the same way. I think ariaSet makes sense.

DOM Partner Backlog Feature Request

Most helpful comment

@aditya2272sharma We were discussing on making working with data-* and aria-* better (just like we did with style), not eliminating them...

All 27 comments

Cool, I'll take a look at this this week.

Last point, assuming you mean dataset = {...}, wouldn't work. dataset is a DOMStringMap and setting it an obj doesn't do anything. So if we decide to do this it'll likely be sharing the logic with style. #1543.

Hi @zpao, is this still on the radar?

Still on the radar but pretty low priority. I'd be interested to see if it could be revived - @chenglou has the most context on why we didn't quite follow through (I think there were memory tradeoffs)

Yeah, pretty low-priority I think:

  • Some allocation were necessary, on one of the hottest code paths of React. I'm not sure how relevant that still is.
  • It's a bit bothersome because of all the intricacies of camelCasing data-*: https://github.com/facebook/react/pull/1446#issuecomment-43452003
  • Not sure whether ariaSet makes sense. aria-* is a fixed list of attributes and it seems weird to start grouping them now.
  • People rarely use data-* in react now, maybe?

People rarely use data-* in react now, maybe?

I've written a ton of React code this year and I don't remember ever using it. Maybe no point making it easier.

Regardless of whether or not it is rarely used I think data taking an object like style is the correct approach (that's the representation in JS too). But aria are really just prefixed attributes and it seems like it would be detrimental to have them be part of an object.

Our use case is using the data attributes for various tracking libraries that we use in our applications. These libraries look for these attributes & set up their own listeners. I wanted to be able to pass an object in e.g.

// this example
this.props.data = {
  track-action: 'click',
  track-location: 'basket'
};
...
<button data={this.props.data} />

// would render
<button data-track-action="click" data-track-location="basket"></button>

Our current workaround is react-data-attributes-mixin. We might be missing something obvious that we could be using instead?

@jackdcrawford <button {...{'data-track-action': ...}} /> should work just fine.

@syranide :+1:

@syranide @gaearon I think React needs to make dataSet work as expected. There are use-cases for this, even if the vast majority of people is not using this a lot.

For example, we're implementing a very powerful tooltip for React. When the user hovers over a tooltip-able element, we display a tooltip with some content - it would be useful for the tooltip to know the React element/component that it targets, or at least the props accessible at that point. Yet React does not have any way the user can get a React element from an HTMLElement reference (without ref) - and this is okay - but at least we could access some props the user can put in the dataSet or even put there a React element

<a data-tooltip={<div> my custom jsx tooltip</div>>tooltip target</a>

So now when the user hovers over a [data-tooltip] html element (the tooltip target), we could take target.dataSet.tooltip and just make that the contents of the tooltip - now we currently only support data-tooltip="...string..." and inject that as the tooltip html. Or we could do <a data-tooltip-props={...}>tooltip target</a> and make the tooltip read dataSet.tooltipProps as an object.

I am aware this could break context, but it could be a starting point. What do you think?

@radubrehar Why not implement the tooltip in React instead? Also, you cannot store objects in dataset. Using ref you can do what you're trying to do here, attaching data to the DOM nodes (even a reference to the React component) that you can use however you want.., I wouldn't recommend it but that's certainly possible.

@syranide thanks for the quick update. I'll try to briefly explain our scenario:

We want to implement a tooltip that users can just render somewhere in their react component tree and forget about it.

<App>
   <Tooltip target="[data-tooltip]">
  {(targetNode) => {
          return ... // render tooltip content here, based on the current targetNode
  }}
   </Tooltip>
   <Header />
   <AppBody> ... <AppBody />
  <Footer />
</App>

So we want the tooltip to be displayed only for HTMLElements that match the "[data-tooltip]" selector - so all those elements share the same tooltip.
There are two rendering strategies:

  • function as a child - if the tooltip is configured with a single child function, we call that whenever we want to make the tooltip visible for a certain HTMLElement.
  • inject the data-tooltip value as innerHTML of the tooltip - this is not very flexible, but for simple tooltips is does the job.

We don't want to force users of the tooltip add any other refs to their code - and if they use the first rendering strategy, not even a data-tooltip.

Using function as a child - we want to call the function with the tooltip target node (the HTMLElement reference) AND, if it were possible, with the React element/component behind it. But there is no public API/reliable way to get that from a HTMLElement reference.

At this point, our thought was to use dataSet - if React were to implement this properly.
In this way, the users could do something like:

<a data-tooltip={<ul> ... <li /> ... <li /> ... <ul />} >tooltip target 1</a>
<a data-tooltip={<ul> ... <li /> ... <li /> ... <ul />} >tooltip target 2</a> 

<Tooltip target="[data-tooltip]">
{(targetNode) => {
  return targetNode.dataSet.tooltip
}}
</Tooltip>

And this would do the trick.

Would be nice to also have the following

<Tooltip target="[data-tooltip]">
{(targetNode, reactElement) => {
  const props = reactElement.props
  // ... get some important data from the underlying react element props
  // and do some rendering based on it
}}
</Tooltip>

But this not an issue that we cant get the underlying React element behind an HTMLElement without ref, if the dataSet were to work as expected.

Hope this is not too long and too specific - and that React gets some real benefits from implementing dataSet :)

Update

@syranide my code assumptions from above were based on the ability of storing any js value inside dataset - thanks for eye opening on this - digging into this right now.

So doing <a data-tooltip={<ul /> }>tooltip target</a> is definitely not an option then.

I think this will be possible when/if custom attributes land in React

@radubrehar Not without special handling. There's a comment above stating that you can't directly provide an object to element.dataset.

bump

May I ask, since when eliminating native HTML properties became acceptable for frameworks?

People rarely use data-* in react now

I'd blame the inexperience of the React community/programmers for the myopia. React doesn't constitute whole of web. And veterans (yeah, jQuery programmers) would know data properties are often used.

@aditya2272sharma We were discussing on making working with data-* and aria-* better (just like we did with style), not eliminating them...

appreciate your efforts, apologies for the previous comment.

I'd love to practice this if and when dataset is implemented: https://statusok200.wordpress.com/2017/01/10/dataset-in-react/.

Would this be the recommended method against binding or is there a more performant way to do this?

Thanks!

We use data-* as standard way for tools to associate data with an element.
Specifically we use a utility that saves + restores form data in form elements that are labeled with a key in their dataset.

While this problem could be solved entirely using react-based tools, react is not the standard interface, the DOM is the standard.

Here is my +1 for dataset support

If anyone still trying to pass an object dataset to a component, this gist might help.
https://gist.github.com/t1ger-0527/f839a3f59bdcad3300e3e4865bb09027

@gaearon @syranide how would you deal with event handlers in lists without the data-* attribute?

For example,

import React from "react";

class List extends React.Component {
  constructor() {
    super();

    this.state = { list: ["a", "b", "c"] };

    this._onChange = e => {
      this.setState(prevState => {
        prevState.list[parseInt(e.target.dataset.index)] = e.target.value; // you get the idea...
      });
    };
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.list.map((value, index) => (
            <li key={index}>
              <input
                data-index={index}
                value={value}
                onChange={this._onChange}
              />
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

The only alternative solution that I know to this problem is to use arrow functions/function closure to bind the index to a create specific item event handler. BUT doesn't that create a new function for every render? So, if I had a lot of rows, wouldn't that generate a lot of GC garbage?

@leidegre : yes, that would be the preferred approach, and yes, it does create functions in every re-render. Please see React, Inline Functions, and Performance for a discussion of that.

If performance is a real concern for your situation, you can try memoizing your rendering so that you don't have to re-create the bound functions every time you re-render, as well as virtualizing the list so you aren't actually putting all the rows in the DOM.

@markerikson This is an issue that should be resolve by adding option to return an object without render every time the component ( and without render every time an arrow function).
you right that there is an option to minimize it by virtualised the grid or list.
For me this is a big issue, i want to write react in the best way but react not let me the option to write in the best way

In the example above i only want to know on each item in the list you clicked, but i don't want to re render every time the function......

So sorry your answer is not enough for me, i still need to work hard to know each item clicked on the list....

for example you can see
@leidegre example

@markerikson I don't know who gathered statistics on the preferred approach, but my preferred approach is to use the data- attributes rather than doing extra work away from the call-site to memoize a simple event handler.

The React docs on this subject give _zero_ reasoning for their opinion.

Something like a dataSet property will make code much more readable for those that can't use Node.js and JSX.

const e = React.createElement;

e(
  "span", {
  id: "foo",
  className: "bar",
  dataSet: {foo: "bar"},
  style: {color: "red"}
  }
);

Otherwise, you have to wrap data attributes with quotes (which is awful):

const e = React.createElement;

e(
  "span", {
  id: "foo",
  className: "bar",
  "data-foo": "bar",
  style: {color: "red"}
  }
);
Was this page helpful?
0 / 5 - 0 ratings