Preact: What's the reason behind using class instead of className?

Created on 22 Mar 2016  路  20Comments  路  Source: preactjs/preact

I've been thinking, why class but not className? If I'm not mistaken, className in react used due to Element.className and not Element.class. Why class here? If suddenly someone would try to switch-and-go to react or similar, he will experience huge compatibility issues.

Or there is no goal in the end - for preact to be total drop-in-replacement but to be something new?

Most helpful comment

Excellent question. To start, Preact is an implementation of the core value behind React - the goal is not to be a perfect drop-in replacement for React, since that would require re-implementing things that the React team has deprecated or moved away from.

However, since a "React alternative" is very similar to being an "alternative implementation of React", I put together preact-compat, which smooths out the differences. I think this approach is ideal, because it keeps the Preact core lean and explicitly classifies each feature as being either part of Preact itself, or part of an effort to support React's exact API and intricacies.

The decision to use className was made years ago internally at Facebook, when React wasn't even public. From what I have gathered, the reasons for using className instead of class were that it matches the DOM className property, and that it avoids using the reserved word class. The latter is a reasonable point, but I can count the number of times I've run into this on one hand, and 100% of those times occurred when authoring libraries. In those cases, one typically uses classnames() or similar anyway.

The first point about DOM property parity has actually been mostly discarded as React has evolved. In the latest 15.x release, React actually uses attributes instead of properties in many cases, similar to Preact. There are an incredibly small number of cases where the DOM property for a given HTML attribute uses a different name - className~class and htmlFor~for are two examples. Another would be passing a String value for the style JSX prop, which internally uses style.cssText (doesn't match at all). I don't see anyone using htmlFor="" in React (maybe I'm missing it), and yet className remains universally special-cased.

In addition to these disparities, the semantic meaning of className and class are identical: both are Strings that resolve to the same value. It does so happen that the className property is faster to manipulate than the class attribute - from the perspective of Preact, this is the only reason to differentiate between the two, and is strictly an internal optimization. If the single if statement pertaining to that optimization were to be removed, the value would fall through to setAttribute and the net effect would be purely a small performance difference.

However, if you compare this to React's position on the matter - everyone who learns React+JSX must also understand className - someone who normally writes HTML must now remember that React uses a different attribute name (prop name) for exactly the same purpose. To me, that seems like a silly issue to take a stand on.

I think it's admirable that so much thought went into the initial decisions that were made around that time, but I also think it's a good idea to continually revisit decisions as a project evolves. For those who are new to JSX, className is something that needs to be learned, and since it serves no purpose internally that seems like unnecessary baggage.

Finally, I should mention that Preact will happily consume className props/attributes if you use them. The JSX reviver simply changes the name to class and continues on as it would have if you'd used class.

I hope I don't sound too dismissive or opinionated here, this is a discussion I've had with many people over the past few months and I enjoy talking about it. Cheers!

All 20 comments

Excellent question. To start, Preact is an implementation of the core value behind React - the goal is not to be a perfect drop-in replacement for React, since that would require re-implementing things that the React team has deprecated or moved away from.

However, since a "React alternative" is very similar to being an "alternative implementation of React", I put together preact-compat, which smooths out the differences. I think this approach is ideal, because it keeps the Preact core lean and explicitly classifies each feature as being either part of Preact itself, or part of an effort to support React's exact API and intricacies.

The decision to use className was made years ago internally at Facebook, when React wasn't even public. From what I have gathered, the reasons for using className instead of class were that it matches the DOM className property, and that it avoids using the reserved word class. The latter is a reasonable point, but I can count the number of times I've run into this on one hand, and 100% of those times occurred when authoring libraries. In those cases, one typically uses classnames() or similar anyway.

The first point about DOM property parity has actually been mostly discarded as React has evolved. In the latest 15.x release, React actually uses attributes instead of properties in many cases, similar to Preact. There are an incredibly small number of cases where the DOM property for a given HTML attribute uses a different name - className~class and htmlFor~for are two examples. Another would be passing a String value for the style JSX prop, which internally uses style.cssText (doesn't match at all). I don't see anyone using htmlFor="" in React (maybe I'm missing it), and yet className remains universally special-cased.

In addition to these disparities, the semantic meaning of className and class are identical: both are Strings that resolve to the same value. It does so happen that the className property is faster to manipulate than the class attribute - from the perspective of Preact, this is the only reason to differentiate between the two, and is strictly an internal optimization. If the single if statement pertaining to that optimization were to be removed, the value would fall through to setAttribute and the net effect would be purely a small performance difference.

However, if you compare this to React's position on the matter - everyone who learns React+JSX must also understand className - someone who normally writes HTML must now remember that React uses a different attribute name (prop name) for exactly the same purpose. To me, that seems like a silly issue to take a stand on.

I think it's admirable that so much thought went into the initial decisions that were made around that time, but I also think it's a good idea to continually revisit decisions as a project evolves. For those who are new to JSX, className is something that needs to be learned, and since it serves no purpose internally that seems like unnecessary baggage.

Finally, I should mention that Preact will happily consume className props/attributes if you use them. The JSX reviver simply changes the name to class and continues on as it would have if you'd used class.

I hope I don't sound too dismissive or opinionated here, this is a discussion I've had with many people over the past few months and I enjoy talking about it. Cheers!

Wow this is really good explanation and I'm with you about deprecation and unnecessary baggage.

Thank you!

Also, I think this exactly explanation should be in FAQ part of the website. It's magnificent.

I'm getting into preact to create my own elements, while still using the libraries out there like : https://github.com/STRML/react-grid-layout which depend on React.

Is there a reccomended way to use preact (with its class prop) with preact-compat+React library (with their className prop?)

@AlexAThomas Preact support both properties. If you prefer className, you can use it as you would normally.

Preact intentionally defaults to class because React's original reasons for choosing className ended up not making much sense, though it took a couple of years to see this and they're stuck with the name now. className was originally chosen because that's the DOM property name, but in reality the value of that prop gets transformed prior to being used anyway - this means the name is shared, but the semantics are not. Another original reason was because props.class throws in IE8 - however, this doesn't provide justification anymore since IE8 is no longer relevant/supported, and Babel & UglifyJS auto-escape keyword properties (props['class']) to avoid the bug.

Preact also ships with object class / className support by default, which further separates the semantics of JSX class/className from the DOM className property, which can only be a String.

<a class={{ foo:true, bar:false }} /> // equivalent to: <a class="foo" />

Let me know if this answers your question - I know it can be strange to see intentional differences in a library that is otherwise so similar to React, but these are part of the reason Preact exists. It is a library that has no growth baggage, and thus can make decisions that build on a few years of learning in this space.

@AlexAThomas

If you are just starting out with Preact and writing JSX components, I would recommend you to try to restrict yourself to stateless pure functions as much as you can. They have the exact same API in both libraries and it will very quickly 'force' you into the right mindset. You actually only rarely need state.

Here is what a HelloWorld component would look like:

// first, make sure the JSX processor is in scope
import { h } from 'preact'
// or, import React from 'react'

// then use JSX as you see fit in a pure stateless component
export default function Greet({greeting='Hello, World!', children, ...props}) {return (
  <div {...props}>
    <b>{greeting}</b>
    {children}
  </div>
)}

// use it like so:
var markup = <Greet greeting="Hi there!"><p>How are you doing?</p></Greet>

You can even write these components to be 'universal' in the sense that they could be made to work under React and Preact interchangeably, without needing preact-compat or aliasses in webpack etc. This would involve removing the only framework specific code (the import statements) and agreeing on a global name for the JSX processor function and setting that via a pragma in the babel config. There is an interesting article about this subject if you'd like to know more:
http://jxnblk.com/writing/posts/universal-ui-components/
I tried it out and it does work. I write my JSX components without import statements now. But not really actively using it, I just settled for Preact 馃槈

Small tip to whoever works with IntelliJ (or WebStorm etc.) and gets warned that '_Attribute class is not allowed here_':
Silence it by going to '_Preferences_' > '_Inspections_' > '_Unknown HTML tag attribute_' and adding '_class_' to '_Custom HTML tag attributes_'.

@developit That doesn't seem to be true that Babel auto-escapes reserved words.
I'm seeing that in my Babel output:

var className = props.class || props.className;

@developit I take it that support for preact class as objects was later removed. How does one achieve this ?

This doesn't work anymore

<div
            class={{
              item: true,
              itemSelected: item.selected,
            }}
/>

@nojvek That's true, we removed it with the Preact X release as it seemed wrong to have in core. Pretty much all users were using the classnames or clsx library for that on to of Preact anyways. props.class and props.className are always expected to be a string.

@nojvek maybe you'll find this useful:

I work on a project with tight bundle budget (hence choosing Preact 馃) so I use my own version of classnames (and there may be other reasons to do so - security concerns regarding 3rd parties, the Leftpad debacle...):

utils:

export const classNames = classArr => classArr.filter(el => el).join(' ') // filter falsy values

someComponent:

<div className={classNames(['always', isSpecialCase && 'special'])}>

Ah sweet. That works. Coming from snabbdom the class object notation is natively supported and they use .classList property for diffing which is a set under hood so faster than setting the class attributes on the dom nodes.

I鈥檓 hoping preact also uses classList properly rather than class attribute on the underlying dom nodes.

@nojvek That's very unlikey that we'll switch to classList as it doesn't support all our use cases. See this scenario:

// First render
<div class="foo bar bob baz" />

// second render
<div class="bar baz" />

Right now this is just a property setter:

dom.className = "bar baz"

If we would switch to use classList here, we'd suffer greatly in performance as we'd need to introduce various forms of iterations and checks. The classList is only faster if you can ensure that the shape of the object always stays the same between renders.

// First render
<div class={{ foo: true, bar: true }} />

// Second render, shape changed
<div class={{ foo: true }} />

In the above example we don't know if a class was removed right away. We'd need to manually iterate over the old object and check if the property is not present in the new class object.

The only scenario in which this is more performent would be this one:

// First render
<div class={{ foo: true, bar: true }} />

// Second render, shape stays the same
<div class={{ foo: true, bar: false }} />

Thanks for the explanation 馃憤

I just stumbled into this because Preact internally rewrites className to class which was not obvious to me at all and actually causes a small "issue" for me right now as it's not possible to destructure class from a props object like this:

const { class, ...linkProps } = props;
<Link {...linkProps} />

which I needed because the Next.js Link component doesn't accept class or className props.

Facebook also thought about this transition and dropped it exactly because of these issues with class
https://github.com/facebook/react/issues/13525#issuecomment-671892643

Interesting. Can you open a new issue with a full reproduction so we can investigate and triage?

@DominikGuzei
It's a bit ugly but you can do this:

var props = { 'class': 'test', some: 'other' }
var { ...linkProps } = props // shallow copy
delete linkProps['class'] // ugly but works
console.info(linkProps) // {some: "other"}

+1 on the destructure bit. So Preact adds both class and className to the props if you just pass className? I have no idea how I never stumbled across this issue before...This means if we want to not pass through a class to children we need to pick _both_ props out?

const MyComponent = ({className, ...rest) => (
  <div className={className}>
    <div {...rest} /> <--- gets "class" !!!
  </div>
);

Did I miss something? Again, I can't believe this hasn't caused issues for me yet (maybe it has)

@ansonatloop No, the inner <div> doesn't get class, because we're not passing both class and className around. What we're doing is installing a getter that is non-enumerable (= ignored with spread) for the property that is not present. So the code you posted works as expected and no class is spread to the inner div.

We had a regression in 10.5.0 - 10.5.2 that is now fixed. Just published 10.5.3 moments ago.

@marvinhagemeister Hmm, well, I _am_ on a slightly older version (10.4.7) and with preact-compat that does not seem to exactly be the case.

https://github.com/preactjs/preact/blob/10.4.7/compat/src/render.js#L108

see: classNameDescriptor.enumerable = 'className' in props' which would be true if I pass className in props, meaning both class and className are enumerable.

But nonetheless, you're probably referring to a version in which the above is no longer true. I see https://github.com/preactjs/preact/issues/2772 might be related and after bumping to 10.5.3 I don't see this issue anymore 馃憤

I just stumbled into this because Preact internally rewrites className to class which was not obvious to me at all and actually causes a small "issue" for me right now as it's not possible to destructure class from a props object like this:

const { class, ...linkProps } = props;
<Link {...linkProps} />

Actually this is easy to fix by assigning the property to a different variable name when destructuring:

const { class: className, ...linkProps } = props;
<Link {...linkProps} />

(See the article on Destructuring assignment on MDN.)

@rejc2 Oooh nice one! I never thought about that but it's sure a lot cleaner to do it like that 馃憤

Was this page helpful?
0 / 5 - 0 ratings