In the React world, most developers don’t think twice before using closures in their component render methods, especially when mapping over lists.
But what if I told you there's an underutilized, yet powerful alternative that can help with performance, readability, and even better integration with tools?
Let’s talk about HTML data-* attributes: a feature that’s rarely used by frontend developers, but deserves a second look.
❓ What are data-* Attributes?
HTML’s data-*
attributes allow you to embed custom data inside DOM elements. In plain HTML:
<div data-user-id="123" data-role="admin">John</div>
In React, you can use them the same way:
<div data-user-id={user.id} data-role={user.role}> {user.name} </div>
They are accessible in event handlers via the dataset object:
e.currentTarget.dataset.userId;
🧠 The Common Pattern: Closures in .map()
Let’s say you’re rendering a list of items:
{items.map((item) => ( <button key={item.id} onClick={() => handleClick(item.id)}> {item.name} </button> ))}
This works fine. It’s readable and easy to write.
But behind the scenes, you're creating a new function for each item on every render: a closure that captures item.id
.
In many apps, this has no practical impact. However...
⚠️ The Downsides of Closures
While closures are a fundamental part of JavaScript and React, using them inside .map()
can have drawbacks:
1. Unnecessary Re-Renders
If you're using React.memo
, React.useCallback
, or virtualized lists (like react-window), new function references can cause unwanted re-renders. Since the function is recreated every time, memoization doesn't help.
2. Harder to Optimize
Imagine you're building a highly interactive list that renders hundreds of items. Minimizing re-renders becomes important, and inline closures can work against that.
✅ The Alternative: data-*
Instead of creating a closure for each item, attach metadata directly to the DOM using data-*
:
function handleClick(e) { const id = e.currentTarget.dataset.id; console.log("Clicked item:", id); } {items.map((item) => ( <button key={item.id} data-id={item.id} onClick={handleClick}> {item.name} </button> ))}
Single function reference → plays nicely with
React.memo
orReact.useCallback
Improved performance for large lists or re-render-sensitive components
🤔 So Why Isn’t Everyone Doing This?
Because closures are easy, intuitive, and performant enough for most apps. data-*
feels a bit "old-school," and it’s rarely mentioned in modern React/Frontend tutorials.
But in situations where performance or memoization matters, this approach can be a hidden gem.
✨ In Summary
While data-*
attributes aren’t a common tool in a React developer’s toolbox, they offer real advantages in specific scenarios:
Reduce unnecessary function creations
Improve memoization and rendering performance
Enable simpler and cleaner event handling
They’re not a replacement for closures, but they’re a great alternative when performance or architecture calls for it.
Have you ever used data-*
attributes like this? Or do you usually stick with closures? I’d love to hear your thoughts and experiences!
I hope you found this post interesting. Let me know what you think in the comments 👇
Top comments (44)
Im not a fan of this approach because only serialisable data can be inserted into the DOM and then extracted back out into JS, and while you can simply use the ID to
find
the item in the array but doing so will cause so many loops that you lose any performance gained by removing the closures.While always, it depends on the situation, so it's nice to have it in your pocket should the situation arise.
That's why functional components in React are sucks.
Also most React developers do not even know that
useMemo
useEffect
useState
and all other React hooks it is not only "magic hook" - this is functions which do some calculations inside.Take a look at this great article
mbrizic.com/blog/react-is-insane/
I personally try to get rid of using React hooks, I'm using MVVM and MobX which works with classes
Also if we will use
data-*
attributes then this is killing any types in TypeScript, so this is not good.Also modifying
data-*
attributes is not performant (calls reflow after change)Every class component should have render function with all drawbacks of function components and without ability to use hooks but only terrible on componentShouldRecieveProps, already ReceivedProps, maybeItsHereTimeToHandlePropsButMaybeNot, componentWillUnmount etc. Do not remember exact names but it was very close to it
Yes, agreed with you, but in hook way you must keep in mind how works useEffect with dependencies and how works closures in use* hooks. How simple works functional component and why declaration a lot of hooks in functional component body can be hurtful for performance.
This is a terrible article lol
Hard disagree. This article is extremely accurate representation of what 90% of enterprise and startup codebases contain right now. Anyone who denies it either has no experience or is too far the react cult to realize what’s happening.
So much this
Why ?
React has plenty of issues, but he’s criticising React for problems he created himself because he doesn’t understand it and write shit code
I’m not criticizing React. Actually I love it. I’m just presenting another way of doing things 😙
You should. at this moment, frameworks like Vue or Angular are now better and more modern / performant than React.
youtu.be/DLa2FQQzCr4?si=rSh0tlV-R5...
tldr: this is not a great article
That video demonstrates and perfectly proves the points in the article by showcasing snippets from his own code that contain useEffect hooks and also sharing examples from large projects that contain similar problems…
TLDR: that video is an amazing demonstration why the article is accurate and how blind React worshippers are for their own misery.
I am inclined to comment, just use Svelte and be happy 😁
It's also not typesafe, and for large data or long lists you will be serialising the data into strings ahead of time, before you even need it. It's cheaper to make a new anonymous function than serialising large data sets to string.
Keeping the data in memory and only accessing when needed is sometimes more preferable.
With the rise of SSR this may become more important, however I remember the time when getting data out of the DOM was a big push. The thing is, you always want a single source of truth for your data. If you put things in the DOM and have things in memory, you will have to figure which one to trust when they fall out of sync. Moving everything to JavaScript is easy, because AJAX goes straight to JS. The DOM cannot query the server and only understands strings. So if the DOM is your source of truth, how are you going to get data into and out of it? Will it be able to keep up?
I like this, I can see the uses for it.
But like everything in writing code, there are many ways to skin a cat.
And as engineers, we need to be aware of the security risks and use this with caution.
OK for UI-related non-sensitive data
Not OK for anything security-sensitive (roles, tokens, permissions)
Safe Use Example:
<div data-product-id={product.id} onClick={handleClick}>{product.name}</div>
function handleClick(e) {
const id = e.currentTarget.dataset.productId;
// Use it to fetch more info securely from backend
}
Unsafe Example:
<div data-auth-token={user.token}>...</div> // Don't leak sensitive data
<div data-role="admin" onClick={showAdminPanel}> // Role-based access is not secure on frontend
This isn't quite true. A new function is created each render, but this is the only function that gets called for every item map goes over, not per ID.
For buttons, checkboxes, radio buttons you could also use the
name
andvalue
attributes.Such a simple trick but so underrated..
I couldn’t get past the hyperbolic, made up statistic “99% of devs don’t do…” 😆
Some comments may only be visible to logged-in visitors. Sign in to view all comments.