JedWatson / classnames

A simple javascript utility for conditionally joining classNames together
MIT License
17.54k stars 556 forks source link

Being able to spread in React {...cx('awesome-style')} #132

Closed jeroenransijn closed 5 years ago

jeroenransijn commented 7 years ago

The idea I am playing with is to save a couple of characters of typing . I am not sure if this really something worth pursuing, but it has been somewhat of an annoyance since I often mistype className and react props can quickly build up.

import React from 'react'
import cx from 'classnames/react-spread'

// Only type className once inside the component
const MyComponent = ({ className, ...props }) =>
  <div {...cx(className, 'another-class')} />

// No need to type className at all
const MyOtherComponent = () => <div {...cx('awesome-style')} />

It would work exactly the same as classNames right now, but instead it would return an object:

import cx from 'classnames/react-spread'

const obj = cx('magic') // => { className: 'magic' }
obj.className // => 'magic'

I was thinking of calling it something like classnames/react-spread.

If maintainers think this is a dumb idea, feel free to close this issue.

newyork-anthonyng commented 7 years ago

@jeroenransijn I have definitely typed in all variations of className into my JSX (classnames, classname, classNames, className). I wonder if I should use a better linter to catch these 🤔

Also, I think it would be simple to write your own wrapper around classnames, and use that instead. It would look something like:

// wrappedClassname.js
import cx from 'classnames';

function wrappedClassname() {
  const newArgs = Array.prototype.slice.call(arguments);
  return {
    className: cx(newArgs),
  }
}

export default wrappedClassname;
jeroenransijn commented 7 years ago

@newyork-anthonyng I always get the wrong autocomplete for className. I am currently trying out something like this in combination with glamor:

import { css } from 'glamor'
import isPlainObject from 'lodash.isplainobject'
import cx from 'classnames'
import TextStyles from './TextStyles'

const cn = (...classes) => {
  return {
    className: cx(classes.map(x => {
      return isPlainObject(x) ? css(x).toString() : x
    }))
  }
}

const MyComp = ({ className, ...props }) => <div {...cn(className, TextStyles.heading300)} />

Your example is pretty much what I had in mind.

alexsasharegan commented 7 years ago

I think @newyork-anthonyng has a solid answer. Could also skip a line and just apply the wrapper function arguments as well:

// wrappedClassname.js
import cx from 'classnames';

function wrappedClassname() {
  return {
    className: cx.apply(null, arguments),
  }
}

export default wrappedClassname;
mrchief commented 6 years ago

If the main goal is to save characters while typing, a much better idea (IMHO), would be to use classnames-loader:

Without loader:

import styles from './styles.css';
const cx = classNames.bind(styles);

...

<div className={cx('className')}>...

With loader:

import cx from './styles.css';
...

<div className={cx('className')}>...

Of course, you can still pursue your idea to stave off few more characters on top of this! :)

dcousens commented 6 years ago

If you wanted this as /spreadable, or something, OK, but, is this idiomatic?