Theme-ui: sx prop on custom components not working

Created on 30 Sep 2019  路  6Comments  路  Source: system-ui/theme-ui

I've noticed while writing components that the sx prop didn't work on custom components which i'd built myself, but when i went to use it with other plugin libraries (fontawesome for example) it did work as expected. So am i missing something? I can't imagine large libraries like FA are doing something special to accept the sx prop.

An example component i've made (though this seems to affect everything i've written, so i'm curious what's wrong).

/** @jsx jsx */
import { jsx } from 'theme-ui'

interface PropTypes {
  to: string
  variant?: string
  children: string
}

const Button = ({ to, variant, children }: PropTypes) => (
  <a
    href={to}
    className={`button`}
    sx={{
      px: 3,
      py: 2,
      border: 0,
      borderRadius: 4,
      cursor: 'pointer',
      variant: variant ? `buttons.${variant}` : `buttons.primary`,
    }}
  >
    {children}
  </a>
)

export default Button
<Button to='#' variantbutton='primary'>Button text</Button>

What i'd like to do, as an example
```

Most helpful comment

You鈥檒l need to pass the className prop through to the underlying HTML element. The sx prop is converted to className with Theme UI鈥檚 jsx function

All 6 comments

It's not as automagical as you think. It's pretty special but not automagical persay.

You're not passing the sx prop to the Button component...

You can have two sx props though! so on your component add another right afterwards sx={sx} and add sx to your incoming props {to,variant,children,sx}

What @robmoggach
The sx prop must be passed down all the way to the actual HTML element..

Otherwise, it'll get lost & there's no way for Emotion to know what to style!

EDIT: I was wrong! Please see jxnblk's comment below :))

You鈥檒l need to pass the className prop through to the underlying HTML element. The sx prop is converted to className with Theme UI鈥檚 jsx function

@jxnblk
All this time... I'd been passing ...props down thinking it was sx doing the magic.

Woops! 馃槄

So i didn't mention that i'd passed {...props} down before and that didn't work (and i'd already tried sx={sx} for good measure but i had no idea it'd already been converted to a className).

Fails

const Button = ({ to, variant, children }: PropTypes, props: any) => (
  <a
    {...props}
    ...
  </a>
)

Works, but with TS errors

const Button = ({ to, className, variant, children }: PropTypes) => (
  <a
   className={className}
    ...
  </a>
)

So, this wasn't working because of how i was destructuring (not sure if once an arg has been destructured in a function it can be called again?) but if i put in className as a destructured argument it works ... but TS is erroring because i'm never defining className on the parent.

Solution

const Button = (props: PropTypes) => {
  const { to, variant, children } = props
  return (
    <a
     {...props}
      ...
    </a>
  )
}

This works and throws no errors but i will be getting some redundant props. My button will actually end up looking like this, which isn't great and could definitely require some cleaning up but i'm not sure how without causing more errors/ts warnings.

const Button = (props: PropTypes) => {
  const { to, variant, children } = props
  return (
    <a
      href={to}
      className={`button asd-123-Button`} //merging properly, yay!
      sx={{...}}
      to={to} //redundant & obfuscated
      variant={variant} //redundant & obfuscated
      children={children} //redundant & obfuscated
    >
      {children}
    </a>
  )
}

Okay i think i've got a solution which is clean & gives no errors.

Best solution so far

/* imports */

interface PropTypes {
  to: string,
  variant: string,
  sx: object, //we expect a sx prop but never use it below
  className: string, //we expect a className prop but never define one when the component is called
  children: string
}

const Button = (props: PropTypes) => { //props is defined and compared against the interface
  const { to, variant, className, children } = props //then we destructure later, ignore sx because we don't use it
  return (
    <a
      href={to}
      className={className} //merging properly, yay!
      sx={{...}}
    >
      {children}
    </a>
  )
}
<Button to='#' variant='button.primary' sx={{ display: 'flex' }}>Button text</Button>

No duplicated props, and everything is used some way or another.

Going to close as i'm happy with this solution (especially as sx errors on html elements anyway in ts so removing the errors in my own components is 馃憣)

Thanks all!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Everspace picture Everspace  路  3Comments

LeunensMichiel picture LeunensMichiel  路  3Comments

folz picture folz  路  3Comments

moshemo picture moshemo  路  3Comments

vojtaholik picture vojtaholik  路  3Comments