264

I need to add a dynamic class to a list of regular classes, but have no idea how (I'm using babel), something like this:

<div className="wrapper searchDiv {this.state.something}">
...
</div>

Any ideas?

Flip
  • 6,233
  • 7
  • 46
  • 75
Mankind1023
  • 7,198
  • 16
  • 56
  • 86
  • Useful answers for reactjs styling best practice at [link](https://stackoverflow.com/questions/26882177/react-js-inline-style-best-practices/31638988#31638988) – Rahil Ahmad Jan 16 '18 at 04:03

18 Answers18

396

You can either do this, normal JavaScript:

className={'wrapper searchDiv ' + this.state.something}

or the string template version, with backticks:

className={`wrapper searchDiv ${this.state.something}`}

Both types are of course just JavaScript, but the first pattern is the traditional kind.

Anyway, in JSX, anything enclosed in curly brackets is executed as JavaScript, so you can basically do whatever you want there. But combining JSX strings and curly brackets is a no-go for attributes.

daaawx
  • 3,273
  • 2
  • 17
  • 16
dannyjolie
  • 10,959
  • 3
  • 33
  • 28
  • and what in case of multiple classes ? – Pardeep Jain Oct 04 '17 at 11:29
  • 9
    In the case of multiple dynamic classes, all dynamic classes must be coming from the state, i.e. it is not possible to use `element.classList.add("my-class")`; therefore allowing for:`className={\`wrapper searchDiv ${this.state.something ? "my-class " : ""} ${this.state.somethingElse ? "my-other-class " : ""}\`}` – Kevin Farrugia Apr 25 '18 at 13:18
  • `className={'wrapper searchDiv} + this.state.isTrue ? 'my-class' : ' '` This doesn't work. Can anybody tell why? – kryptokinght Dec 02 '18 at 18:28
  • @kryptokinght You can try `className={'wrapper searchDiv ' + (this.state.isTrue ? 'my-class' : ' ')}` – Sumit Patil Nov 26 '20 at 13:10
74

Depending on how many dynamic classes you need to add as your project grows it's probably worth checking out the classnames utility by JedWatson on GitHub. It allows you to represent your conditional classes as an object and returns those that evaluate to true.

So as an example from its React documentation:

render () {

var btnClass = classNames({
  'btn': true,
  'btn-pressed': this.state.isPressed,
  'btn-over': !this.state.isPressed && this.state.isHovered
});

return <button className={btnClass}>I'm a button!</button>;

} 

Since React triggers a re-render when there is a state change, your dynamic class names are handled naturally and kept up to date with the state of your component.

Brad Colthurst
  • 2,515
  • 14
  • 13
  • 8
    The use of classNames for such a simple thing is an overkill. Avoid using it and opt for the simple answer of dannyjolie – checklist Nov 02 '17 at 20:15
  • 4
    Are you sure it's a simple thing? Near always you want to have fine grained full control over what classes are applied to elements. – Dragas Jun 26 '18 at 12:03
  • 6
    @checklist I would argue otherwise, the classnames package is 1.1kB before Gzipping (and half a kB after Gzipping), has no dependencies, and provides a much cleaner API for class name manipulation. There's no need to prematurely optimise something so small, when the API is much more expressive. When using standard string manipulation you have to remember to account for spacing, either before every conditional class name, or after every standard class name. – tomhughes Jul 06 '18 at 08:39
  • classNames is great, I found that reading, adding, and fixing props were easier than manual manipulation as the component grew in complexity. – MiFiHiBye Dec 15 '18 at 00:51
51

A simple possible syntax will be:

<div className={`wrapper searchDiv ${this.state.something}`}>
oligopol
  • 810
  • 11
  • 17
51

Here is the Best Option for Dynamic className , just do some concatenation like we do in Javascript.

     className={
        "badge " +
        (this.state.value ? "badge-primary " : "badge-danger ") +
        " m-4"
      }
Saad Mirza
  • 1,154
  • 14
  • 22
14

Don't think of a solution so complicated.

here is the easiest solution for your problem.

<div className={`wrapper searchDiv ${this.state.something}`}>
   ...
</div>
rahul.taicho
  • 1,339
  • 1
  • 8
  • 18
Daniel Paul
  • 495
  • 1
  • 6
  • 13
8

try this using hooks:

const [dynamicClasses, setDynamicClasses] = React.useState([
    "dynamicClass1", "dynamicClass2"
]);

and add this in className attribute :

<div className=`wrapper searchDiv ${[...dynamicClasses]}`>
...
</div>

to add class :

    const addClass = newClass => {
       setDynamicClasses([...dynamicClasses, newClass])
    }

to delete class :

        const deleteClass= classToDelete => {

           setDynamicClasses(dynamicClasses.filter(class = > {
             class !== classToDelete
           }));

        }
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Hamza Khattabi
  • 549
  • 4
  • 11
  • the `...` spread operator would actually join the string by a `,` comma by default. e.g. `wrapper searchDiv ${[...'c','as']}` => `"wrapper searchDiv c,as"` – Gianfranco P. Aug 31 '21 at 12:52
8

[UPDATED Apr-21 2022: Adding curly brackets before and after backticks to prevent error]

you can simply use the condition for applying the specific class you want

<div className={`wrapper searchDiv ${this.state.something ? "class that you want" : ""}`}>

if you want to use the class of makeStyle you can use it like

<div className={`wrapper searchDiv ${this.state.something ? classes.specifiedClass : ""}`}>

Dika
  • 2,213
  • 4
  • 33
  • 49
Ihtisham Tanveer
  • 338
  • 4
  • 15
7

If you need style names which should appear according to the state condition, I prefer to use this construction:

<div className={'wrapper searchDiv' + (this.state.something === "a" ? " anotherClass" : "")'}>
Sergey
  • 995
  • 4
  • 14
  • 33
3

You can use this npm package. It handles everything and has options for static and dynamic classes based on a variable or a function.

// Support for string arguments
getClassNames('class1', 'class2');

// support for Object
getClassNames({class1: true, class2 : false});

// support for all type of data
getClassNames('class1', 'class2', ['class3', 'class4'], { 
    class5 : function() { return false; },
    class6 : function() { return true; }
});

<div className={getClassNames({class1: true, class2 : false})} />
Tushar Sharma
  • 192
  • 1
  • 15
3

If you're using css modules this is what worked for me.

const [condition, setCondition] = useState(false);

\\ toggle condition

return (
  <span className={`${styles.always} ${(condition ? styles.sometimes : '')`}>
  </span>
)
quarterpi
  • 833
  • 1
  • 6
  • 13
  • Sorry, that was a typo. I meant useState. Edited – quarterpi Feb 15 '21 at 18:28
  • is there any other way around besides this? like in Angular they do have [class.yourClassName]="statement===true". The reason for asking is, I want to avoid the empty string "" for else statement in your code. – Fai Zal Dong Mar 13 '23 at 07:54
2
getBadgeClasses() {
    let classes = "badge m-2 ";
    classes += (this.state.count === 0) ? "badge-warning" : "badge-primary";
    return classes;
}

<span className={this.getBadgeClasses()}>Total Count</span>
1
className={css(styles.mainDiv, 'subContainer')}

This solution is tried and tested in React SPFx.

Also add import statement :

import { css } from 'office-ui-fabric-react/lib/Utilities';
Maximouse
  • 4,170
  • 1
  • 14
  • 28
Harjot Singh
  • 29
  • 1
  • 3
0
const ClassToggleFC= () =>{
  
  const [isClass, setClass] = useState(false);

  const toggle =() => {
       setClass( prevState => !prevState)
  }
  
  return(
      <>
        <h1 className={ isClass ? "heading" : ""}> Hiii There </h1>
       <button onClick={toggle}>Toggle</button>
      </>
   )

}

I simply created a Function Component. Inside I take a state and set initial value is false..

I have a button for toggling state..

Whenever we change state rerender component and if state value (isClass) is false h1's className should be "" and if state value (isClass) is true h1's className is "heading"

  • Hi, can you please explain with a few words how/why your solution works. While for experts "the truth may be in the code", for many visitors a few helpful words are worth alot. Thanks! – Robert Feb 09 '21 at 19:39
0

Even though all of the answers above are quite good, the one that caught my attention is the string interpolation solution. So I'd like to propose a different approach to that using a custom StringBuilder.

Here's the implementation.

class StringBuilder {
  static Builder() {
    class Builder {
      constructor() {
        this.bucket = [];
      }

      append(str) {
        if (str !== null) {
          this.bucket.push(str);
        }
        return this;
      }

      build() {
        return this.bucket.join(' ');
      }
    }
    return new Builder();
  }
}

Then you would just use it like this within a component.

const Field = (props) => {
  const { label } = props;

  const [hasFocus, setFocus] = useState(false);

  const labelClasses = new StringBuilder.Builder()
    .append('form-control')
    .append(hasFocus ? 'has-focus' : null)
    .build();

  const turnOnFocus = () => setFocus(true);
  const turnOffFocus = () => setFocus(false);

  return (
    <div onClick={turnOnFocus} onBlur={turnOffFocus} className="form-group">
      <input
        type="text"
        defaultValue=""
        onChange={setValue}
      />
      <label className={labelClasses}>{label}</label>
    </div>
  );
};

In general, if you have more elements that require dynamic CSS classes, you could just instantiate another builder for that particular element. Additionally, if the class appears in more than one element with the same logic, then the solution would be extracting that logic to a method or function.

Alvaro
  • 11
  • 2
0

A word of caution to those that are using back ticks or concatenation for className in React: You need to have spaces between your class names. Without them, you have a long word that doesn't match any of your css.

Bennington
  • 128
  • 2
  • 6
0

Simply combine class names within an array, remove empty values and join into the string.

<div className={["wrapper", "searchDiv",, undefined, null, this.state.something].filter((e) => e).join(" ")}"}>
...
</div>

Result is the className without extra spaces.

Have a creative day!

Delorean
  • 356
  • 3
  • 11
0

Someone mentioned the classnames library, which looks interesting and useful, but you could do the same thing yourself:

Original Version

…
  // keep your class names in an object,
  // enabled or disabled by the Boolean value
  const classNames = {
    MyComponent: true
    foo: false
    bar: someConditionVar
  };

  // construct the class name, e.g. "MyComponent bar"
  const className = Object.keys(classNames).filter(className =>
      classNames[className]).join(' ');

  // example usage in React JSX
  return (
    <div className="{className} …

Improved, Concise Version

Iterating through the object name/value entries rather than only the keys allows this to be shortened to a single statement:

…
  const className = Object.entries({
    MyComponent: true
    foo: false
    bar: someConditionVar
  }).filter(entry => entry[1]).map(entry => entry[0]).join(' ');

  // example usage in React JSX
  return (
    <div className="{className} …
Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
0

The issue at hand I believe is whether a trailing space in the class name is HTML 5 complaint. The answer is yes.

When specified on HTML elements, the class attribute must have a value that is a set of space-separated tokens representing the various classes

https://html.spec.whatwg.org/multipage/dom.html#classes

A string containing a set of space-separated tokens may have leading or trailing ASCII whitespace.

https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#set-of-space-separated-tokens

With that settled, the obvious solution is to use a template literal as already mentioned.

<div className={`classOne classTwo ${stringOfMoreClasses}`}>

If stringOfMoreClasses is nill, the className attribute will just include the original two classes with a single trailing white space.

Remember React re-runs the component function a lot! This is not a place where you want anything process heavy.

Marc M.
  • 3,631
  • 4
  • 32
  • 53