What is the preferred way of passing styles to children in React & Emotion JS?

4.2k Views Asked by At

I'm just getting started with emotion after working with react-jss for a few years. One thing I'm struggling to find a good example of is passing (some, possibly dynamic) styles to a child component. In react-jss this was pretty trivial:

// In SomeParent.jsx
... imports etc ...

const useStyles = createUseStyles({
  custom: {
    color: 'blue',
  },
})

export const CustomBody = () => {
  const classes = useStyles()
  return <Body className={classes.custom}>some text</Body>
} 

// In Body.jsx
... imports etc...

const useStyles = createUseStyles({
  body: {
    fontSize: 16,
    color: 'black',
  },
})

const Body = ({ children, className }) => {
  const classes = useStyles()
  return (
    <p
      className={clsx(
        classes.body,
        className,
      )}
    >
      {children}
    </p>
  )
}

Essentially, parent components could create styles and pass any number of related class names via a prop (usually className ) to the child, which could then consume them in their own className prop, combining them with any other styles the child component needs.

Emotion definitely does allow this same thing, but I can't figure out how they expect you to do it.

Semantically on the child-side, it seems like the styled tagged template literal takes in emotion styles and passes a className prop to the child, which then would work the same way it does in react-jss. However, I find the styled syntax to be crazy ugly and hard to parse. I'd rather not fill my parent files up with potentially differently styled HOC-wrapped child components.

Functionally, it seems like passing my own prop of emotion styles and composing those styles in the child component most closely mirrors react-jss, but I can't find any examples in the emotion documentation suggesting that this is something they expect you to do. It's not FORBIDDEN or anything, it's just not highlighted. Does something like the following make sense?

// In SomeParent.jsx
... imports etc ...

export const CustomBody = () => {
  return <Body customStyles={css`color: blue`}>some text</Body>
}

// In Body.jsx
... imports etc...

const bodyStyles = css`
  font-size: 16px;
  color: black;
`
const Body = ({ children, customStyles }) => {
  return (
    <p
      css={css`
        ${bodyStyles};
        ${customStyles};
      `}
    >
      {children}
    </p>
  )
}

The composition documentation makes clear that this will work (and it does), but it doesn't specifically showcase passing styles from one component to another the way the styled documentation does. This feels better to me, but I am worried about the note in the string styles documentation which points out that css doesn't return a class string, but instead returns a full object that is "understood by emotion at a low level". If this object isn't memoized by emotion (I haven't tested this myself yet) then I'm going to end up with a bunch of unnecessary re-renders. I could memoize on the class name string that is included as part of that object, but I'm not sure how consistent that is either.

The last thing that might be relevant is the class names API. It looks like this would allow me to create a style and pass it as a className string to a child component (much like in react-jss and avoiding the re-render problem), but again, having to wrap EVERY child component that accepts a style from a parent in the ClassNames component and then dealing with render functions seems crazy.

Am I just missing something here? Has the whole world accepted that styled and the styled component pattern is just the way to go with this stuff? Please let me know how you've solved this problem, or point me to the documentation that I've over looked. Thanks a lot!

1

There are 1 best solutions below

2
On

In case anyone finds this post later, I ran my own tests comparing the performance of each of these methods when creating 1000 p tags and coloring them based on their index.

styled was the most performant, ClassNames was second most performant (with its best performance generally matching the lower end of styled's performance). Passing the css tagged template literal directly was much worse than either of the other two, with performance 1.5x to 2x worse than styled's worst performance.

Conditionally styling by passing props is absolutely slower than static styles, and using the prop filtering for styled only decreases performance a small amount over not filtering (probably a good tradeoff to prevent unnecessary re-renders if that's a potential issue).