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

Switching React class names like a boss with Deciders

uyouthe 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
  })}

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
}

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
})

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>
)

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>
)


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!

Posted on by:

uyouthe profile

Miloslav Voloskov

@uyouthe

🏳️‍🌈 Declarative logic for masses

Discussion

markdown guide
 

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.

 

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 :)

 
 

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

 

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