I often use components that both have an internal className
as well as an optional className
passed in as a prop. The way I've been implementing this is either through (A) a local array that I construct and serialize into the className
prop, or (B) through using React.addons.classSet
.
A:
var classNames = ['my-component'];
if (this.props.className) {
classNames.push(this.props.className);
}
return <MyComponent className={classNames.join(' ')} />;
B:
var classes = {
'my-component': true
};
if (this.props.className) {
classes[this.props.className] = true;
}
return <MyComponent className={React.addons.classSet(classes)} />;
A is slightly less verbose, but harder to use if you have modifier classes as well (e.g. my-component--is-selected
).
I'm suggesting allowing the className to be a string _or_ an array. And if it is an array, React internals would compact and serialize the className array into a className string.
return <MyComponent className={['my-component', this.props.className]} />;
If this is a good idea, I could make an attempt at a PR.
Actually you can just do
return <MyComponent className={['my-component', this.props.className].join(' ')} />;
If this.props.className
is undefined, you will get an extra space in the className (class="my-component "
), but who cares?
You can also write className={'my-component ' + (this.props.className || '')}
. See #1198 though – we're unlikely to do this due to perf.
Thanks @spicyj for that context! I figured this had already been investigated.
Just a chance to revisiting this issue. This feature would be extremely useful instead of having to manipulate the classes beforehand. Also, I noticed a strange behaviour
<div className={['a', 'b']} />
compiles to <div className="a,b" />
which is invalid class definition.
Such feature is not being considered for future releases? /cc @spicyj
Completely agree with @pgom. If array stringify, dividing items by comma, then why we can't just a bit rectify this behavior and use space as delimiter?
I was a bit inaccurate. After walking through the code of React I've learned that className
property ain't processed via custom logic. The "strange" behavior, that @pgom has mentioned, could be described by this construction: ['a', 'b'].toString() === 'a,b'
. So for now React haven't any additional logic for this and implementation could lead to performance leaks (because, might be, we will need some checks for array items to not allow cases like [{}, []]
, for instance), as @spicyj said.
As a solution I can only propose: ['a', 'b'].join(' ')
.
If what you want to achieve is something like this:
const myDivClassNames = ['a', 'b'];
...
<div className={myDivClassNames} /> // <div class="a b" />
... then you could possibly use the classnames utility as follows:
import classNames from 'classnames';
...
const myDivClassNames = classnames('a', 'b');
...
<div className={myDivClassNames} /> // <div class="a b" />
... or maybe:
import classNames from 'classnames';
...
const isCTrue = false;
const myDivClassNames = classnames(
{ 'a': true },
{ 'b': true },
{ 'c': isTrue },
);
...
<div className={myDivClassNames} /> // <div class="a b" />
className={["a", "b"]}
className={classnames(["a", "b"])}
className={["a", "b"].join(" ")}
Why can't we have the first way?
To avoid breaking anything, it could be
classNames={["a", "b"]}
I just stumbled in to this issue, so wrote a simple helper function:
const classNames = (arr) => arr.filter(Boolean).join(' ');
then you can do you pass anything to the array that generates a string, and not have to worry about false
s etc being output:
<a className={classNames([linkStyle, props.selectedIndex === index && 'is-selected'])}>
I'm using destructuring for that:
className={classNames(...["a", "b"])}
I use template string. Works fine for me.
<Component className={`${firstClassName} ${secondClassName}`} />
I know there are a couple of ways to do this but I think it would result in much cleaner syntax for className to accept an array.
Can anyone explain the difficulty in implementing this? Vue seems to have managed it with no challenge.
I'm doing...
toClassNames.ts
export default (values: string[]) => values.join(' ');
button.tsx
import toClassNames from '../functions/toClassNames';
classes =
[
"btn",
"btn--large"
];
render = () => <a className={toClassNames(this.classes)}>{this.props.children}</a>
would be mighty nice for className just to take an array tho :)
does the className read the arr of classes one by one ? so there a dynamic loop that iterates over the arr of classes and executes all the classes inside it ? if so , it means loop keeps working and allocates space in the memory and that would slow the performance or I'm just blah blah ... and don't know what I'm talking about :) ?
Most helpful comment
Actually you can just do
If
this.props.className
is undefined, you will get an extra space in the className (class="my-component "
), but who cares?