4

I was thinking about how to code TailwindCSS cleaner in React. Since Tailwind is utility-first, it makes us inevitably end up with components (ex: className="w-full bg-red-500"). So, I tried to create a utility like this:
utils/tailwind.ts

const tw = (...classes: string[]) => classes.join(' ')

and call it inside:
components/Example.tsx

import { useState } from 'react'
import tw from '../utils/tailwind'

const Example = () => {
  const [text, setText] = useState('')

  return (
    <div>
      <input onChange={(e: any) => setText(e.target.value)} />
      <div
        className={tw(
          'w-full',
          'h-full',
          'bg-red-500'
        )}
      >
        hello
      </div>
    </div>
  )
}

But, it will cause tw() to be re-called as always as text state is updated.

So, I decided to wrap tw() function using useMemo to prevent re-call since the tw() always returns the same value. But the code is like this:

import { useState, useMemo } from 'react'
import tw from '../utils/tailwind'

const Example = () => {
  const [text, setText] = useState('')

  return (
    <div>
      <input onChange={(e: any) => setText(e.target.value)} />
      <div
        className={useMemo(() => tw(
          'w-full',
          'h-full',
          'bg-red-500'
        ), [])}
      >
        hello
      </div>
    </div>
  )
}

Is it correct or good practice if I put useMemo like that? Thank you .

kind user
  • 40,029
  • 7
  • 67
  • 77
mnrendra
  • 162
  • 1
  • 1
  • 8
  • 1
    Calling it inside the JSX element syntax is the same as calling it before the `return` expression, storing the result in a temporary variable, and passing that as the attribute value. No difference. Of course the rules of hooks still apply, you can't do that in a conditional rendering. – Bergi Nov 20 '22 at 02:32

2 Answers2

2

Is it correct or good practice if I put useMemo like that?

Short answer - yes.

Long answer - it depends. It depends on how heavy the operation is. In your particular case, joining a couple of strings may not be such heavy calculation to make the useMemo worth to be used - it's good to remember that useMemo memoizes stuff and it takes memory.

Consider example below. In the first case, without useMemo, the tw function will be called with every App re-render, to calculate new className. However, if useMemo is used (with empty dependency array), tw will not be called and new className will not be calculated even if the App re-renders, due to the basic memoization. It will be called only once, on component mount.

Conclusion - it's a good practice to use useMemo, but rather for heavy operations, like mapping or reducing huge arrays.

export default function App() {
  const [_, s] = useState(0);

  return (
    <div className="App">
      <div className={tw(false, 'w-full', 'h-full', 'bg-red-500')}>div1</div>
      <div
        className={useMemo(
          () => tw(true, 'w-full', 'h-full', 'bg-red-500'),
          [],
        )}
      >
        div2
      </div>

      <button onClick={() => s(Math.random())}>re-render</button>
    </div>
  );
}

Playground: https://codesandbox.io/s/distracted-liskov-tfm72c?file=/src/App.tsx

kind user
  • 40,029
  • 7
  • 67
  • 77
  • Thank you `@kind user` . So, if the project will be bigger and because `useMemo` takes memory also the `tw()` function always only joins strings, so which one do you prefer? use `useMemo` or not? Thank you . – mnrendra Nov 20 '22 at 04:58
  • @mnrendra Personally I would not use useMemo on joining strings. Rather as I mentioned above - on mapping or reducing huge arrays. But still it depends on how many elements you are joining. If it's about hundreds - it's worthy for sure. If just a couple - it's not. (Comment edited) – kind user Nov 22 '22 at 00:02
-1

The issue here is that React will re-render the component every time it's state changes. (each time you setText).

If you want to prevent that from happening, then see if you really need this re-render hence what do you really need the input text for?

you do not HAVE to use state here to use the input value. you could call another function on change which will not update the state, and use the input value there for whatever you need. for example:

const Example = () => {

  const onInputChange = (e) => {
    const text = e.target.value

    // do something with text
  }


  return (
    <div>
      <input onChange={(e: any) => onInputChange(e)} />
      <div
        className={useMemo(() => tw(
          'w-full',
          'h-full',
          'bg-red-500'
        ), [])}
      >
        hello
      </div>
    </div>
  )
}
Tomer_Ra
  • 123
  • 1
  • 11