loading...
Cover image for Switching React class names like a boss with Deciders

Switching React class names like a boss with Deciders

mvoloskov profile image Miloslav Voloskov ・3 min read

TL;DR: You can switch a class names of React components with deciders and it feels like a breeze.

Imagine you have a Header component. Its class name is always header, but on a mobile device, it should be also mobile. Oh, and if the width prop is less than '400px', it should be also narrow. Oh, and it should be hidden when either hidden prop is truthy but not equals to "false" string, and it should be also fixed when it isn't either narrow or mobile.

Wow. Such a mess.

But wait a minute. What if I tell you that you can tackle this problem with just five lines of pretty readable code?

className={ decide(styles, {
    header: true,
    mobile: props.isMobile,
    narrow: parseInt(props.width) < 400,
    hidden: (props.hidden !== "false" && Boolean(props.hidden)),
    fixed: parseInt(props.width) >= 400 || !props.isMobile
  })}
Enter fullscreen mode Exit fullscreen mode

Introducing Deciders

Decider is something that makes decisions.

I was fed up making it manually, so I've created a library to tackle that mess. How? After all, we either apply the class name or not. So I thought, what if we can declare class names as the keys and the conditions should be values?

{
  header: true,  // will be always applied
  mobile: props.isMobile  // will be applied according to a prop
}
Enter fullscreen mode Exit fullscreen mode

The result class names should be either "header" or "header mobile".

The thing above is just an object. As long as it's not some kind of magical class instance, you can even generate it. You can return it from a function or promise. You can bring this from your REST API. This is simple and declarative.

Let's call this kind of object a decision matrix.

Now, we need to get this to work.

import decide from 'decider'
import styles from './header.module.css'

const classNames = decide(styles, {
  header: true,
  mobile: props.isMobile
})
Enter fullscreen mode Exit fullscreen mode

Here you go. It's meaningful. It's simple and understandable. You can export it. The HTML and CSS are declarative, so as deciders.

Let's apply the styles:

export default props => (
 <header className={ classNames }>
   ...
 </header>
)
Enter fullscreen mode Exit fullscreen mode

But what about order? Well, the class names order doesn't matter in HTML, so it doesn't matter in deciders either.

Let's solve the header class names problem I declared at the beginning of the article! Here we go:

import decide from 'decider'
import styles from './header.module.css'

export default props => (
 <header className={ decide(styles, {
    header: true,
    mobile: props.isMobile,
    narrow: parseInt(props.width) < 400,
    hidden: props.hidden !== "false" || props.hidden,
    fixed: parseInt(props.width) >= 400 || !props.isMobile
  })} >
   ...
 </header>
)


Enter fullscreen mode Exit fullscreen mode

Five strings of purely declared decisions.

Differences from classnames

  • Decide is a pure function. It works without that bind magic, so you can use it virtually anywhere.
  • Smaller bundle size: Decider is just 171 bytes away!
  • Syntax and usage are much easier to adopt

Wrapping up

  • Switching class names manually is error-prone and overall not a pleasant thing to do
  • You can switch class names easily with deciders
  • If you want to do this, you should declare a decision matrix – an object which has class names as keys and booleans as values
  • Decider is a decide(styles, decisionMatrix). It returns a class names' string
  • You pass this string as a className prop to your component and it works

I want to adopt this right now!

Here you go.

Alright, that's it! Keep your code clean, meaningful and understandable!

Discussion

pic
Editor guide
Collapse
depstein92 profile image
Daniel Epstein

General question. Wouldnt it pollute props in large projects? Its pretty cool but I feel if I had a large project (especially if Im using Redux) that it would add on to the amount of props on a large component.

Collapse
mvoloskov profile image
Miloslav Voloskov Author

it has nothing to do with props. You just can switch class names according to props or anything else with decider, and that's it :)

Collapse
depstein92 profile image
Daniel Epstein

Ok thank you! :)

Collapse
craigmc08 profile image
Craig McIlwrath

Shouldn't hidden be defined by props.hidden !== "false" && props.hidden instead of or? Because, using or, "false" will still make that expression true.

Collapse
mvoloskov profile image
Miloslav Voloskov Author

You're right. This should be (props.hidden !== "false" && Boolean(props.hidden))