emotion: Unknown prop on DOM element

  • emotion version: 6.0.3
  • react version: 15.6.1

Relevant code:

const Avatar = styled('img')`
  width: 96px;
  height: 96px;
  border-radius: ${props =>
    props.rounded ? '50%' : 0};
`

Example above is from https://emotion.sh/.

What you did: Tried to render the example from above using

<Avatar rounded />

What happened: I got the following error in my console: Unknown prop rounded on tag. Remove this prop from the element.

Problem description: I’m not sure whether this is a problem due to my configuration/implementation or if this is a problem in the library. I assumed this should work without any errors, since i copied the exact example from https://emotion.sh/. However when scanning emotion’s source code i found that styled just blindly merges/spreads all the props onto the DOM element.

Suggested solution: Whitelist the allowed props for each DOM element, which is the way styled-components is doing it at the moment.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 18
  • Comments: 37 (15 by maintainers)

Commits related to this issue

Most upvoted comments

My temporary solution:

const Avatar = styled('img')`
  width: 96px;
  height: 96px;
`

const RoundAvatar = styled(Avatar)`
  border-radius: 50%;
`

props.rounded ? <RoundAvatar> : <Avatar>

I also came across that issue, in my case with react-bootstrap’s ListGroupItem, which also passes through all the props.

What do you think about an official omitProps option to styled?

const ListItem = styled(ListGroupItem, { omitProps: ['color'] })`
  color: ${props => props.color || '#000'};
`;

My current workaround doesn’t feel right nor readable:

const ListItem = styled(({ color, ...props }) => <ListGroupItem {...props} />)`
  color: ${props => props.color || '#000'};
`;

I could take a stab on a PR if you like that omitProps proposal.

I’m not familiar with grid-styled/emotion, but looking at the code I would guess that variant, justifyContent and alignItems are recognised props for their Flex API, and therefore they will take care of ensuring these don’t propagate to the underlying DOM.

However, isOpen is something I assume is what you have added and is thus not recognised by the grid-styled/emotion Flex component. Therefore, styled (from react-emotion) will pass everything through it doesn’t recognise – as it doesn’t know if you are wrapping a base DOM node, or a custom component, and therefore can’t know when or what to prevent propagating through.

However, you can manually tell styled which props is should propagate and which it shouldn’t, although the functionality doesn’t seem to be well documented yet: See https://github.com/emotion-js/emotion/issues/655 for more info.

To fix the warning here, I think the following should do the job:

import styled from 'react-emotion'
import isPropValid from '@emotion/is-prop-valid'
import { Flex, Box } from 'grid-styled/emotion'

const StyledFlex = styled(Flex, { shouldForwardProp: isPropValid })`
	background: ${({ variant, theme }) => theme.colours[variant || 'lightest']};
	position: relative;
	padding-left: ${({ isOpen }) => (isOpen ? '10rem' : 0)};
	transition: padding-left 0.2s ease;
`

I don’t know if this is a ‘good’ solution but I’ve been using the following and getting by without any errors:

const e = React.createElement;

/* manually pull off the props that you will be using 
 * and then forward everything else to the createElement call
 */
const Button = styled(
  ({ backgroundColor, children, ...props }) => e("button", props, children)
)`
  background-color: ${props => props.backgroundColor};
`;

<Button backgroundColor="green">No Error!</Button>

edit

The following helper function can help if the above is a little tedious:

const e = React.createElement;

/**
 * filter helper function 
 * @param {*} tag - they type of component you want, div, button, etc
 * @param {string[]} whitelist - the props that you don't want to forward
 */
const filter = (tag, whitelist) => ({ children, ...props }) => {
  // this probably isn't that safe, but it works as a hack
  whitelist.map(i => delete props[i]);
  return e(tag, props, children);
};

// use by passing in filter to the styled function
const Button = styled(filter("button", ["backgroundColor", "color"]))`
  background-color: ${props => props.backgroundColor};
  color: ${props => props.color}
`;

You could also have the tag be curried if you didn’t want to pass that it (or if you make a lot of the same type of component)

// modified helper
const filter = tag => whitelist => ({ children, ...props }) => {
  whitelist.map(i => delete props[i]);
  return e(tag, props, children);
};

// create the buttonFilter function
const buttonFilter = filter("button");

// use the button filter function - now you only have to pass in the array of filtered props
const Button = styled(buttonFilter(["backgroundColor", "color"]))`
  background-color: ${props => props.backgroundColor};
  color: ${props => props.color}
`;

Perfect thank you! For anybody stumbling across this issue, take a look at #655.

A simple workaround I’ve found is to just prefix props only used for styles with data-. This will prevent the error being thrown.

It’s not ideal, but it works.

Sounds like this should be fixed looking at the above, but with emotion 9.0.1 I still get an error e.g. with a div:

const Container = styled<{ backgroundColor: string }, 'div'>('div')(props => ({
  backgroundColor: props.backgroundColor,
}));
<Container backgroundColor={colors[0]}>
Warning: React does not recognize the `backgroundColor` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `backgroundcolor` instead. If you accidentally passed it from a parent component, remove it from the DOM element.

Any advice? I am using this with react-static if that could make any difference…

That’s actually a pretty good solution, @divyagnan. However i would like to see this solved from the library instead. Currently i’m just directly using the css() function in my components instead of using styled(), passing the relevant props to css() and composing a style object (resulting in classnames) myself.

Any update about this? The code below is just an example:

const StyledInputField = styled('input', { shouldForwardProp: isPropValid })(({ leftElement }) => ({ 
  paddingRight: leftElement ? 40 : 0
}));

Since leftElement is not a valid prop, I cannot access this prop after shouldForwardProp validation, but if I remove it, I receive the React does not recognize the leftElement prop on a DOM element. warning.

This feature already is in emotion - check out shouldForwardProp

great stuff @divyagnan

FWIW you can use recompose’s mapProps

https://github.com/acdlite/recompose/blob/master/docs/API.md#mapprops

const omitProps = keys => mapProps(props => omit(keys, props))

styled(omitProps(['color'])(Button))`
  color: blue;
`

@ai I’ve been writing more and more emotion code and thinking on how I want to handle this in the core. Right now I’m using this setup.

// base.js
import React from 'react'
import styled from 'emotion/react'
import { omit } from 'emotion/lib/utils'
import { space, width, fontSize, color } from 'styled-system'

import theme from './theme'

const defaultExcludedProps = [
  'm',
  'mt',
  'mr',
  'mb',
  'ml',
  'mx',
  'my',
  'p',
  'pt',
  'pr',
  'pb',
  'pl',
  'px',
  'py',
  'fontSize'
]

export default (tag, omitProps = []) => styled(props =>
  React.createElement(tag, omit(props, defaultExcludedProps.concat(omitProps)))
)`
  ${space};
  ${width};
  ${fontSize};
  ${color};
  color: ${theme.colors.gray[8]};
  font-family: 'Oxygen', sans-serif;
  box-sizing: border-box;
`

and usage

import React from 'react'
import styled, { css } from 'emotion/react'
import base from '../base'
import theme from '../theme'

export default styled(base('input', ['omit', 'these', 'props']))`
  position: relative;
  display: block;
  width: 100%;
  border-radius: 0;
  background: ${theme.colors.gray[0]};
  font-weight: bold;
  -webkit-appearance: none;
  border: none;
  border-bottom: 1px solid ${theme.colors.green[5]};
  outline: none;
`

I’ve come to really like this pattern but I’m not sure how to make this a solution that works for everyone.

@thangngoc89 uh, so you clean all non-DOM props automatically? Awesome!

@tkh44 Wow, it was fast! I like Emotion community 😃.

Also, we may need docs for it since it is not clear.

I’ve been using glamor jsxstyle recently with some custom options that allow me to set global custom style props to strip out things like marginVertical, elevation, etc… similar to what you have above. Maybe you could have something similar. Some way to set global props or something. Could it be a babel option? Or maybe you create your own emotion? Super naive example:

import createEmotionFactory from 'emotion/create'
export default createEmotionFactory(['mv', 'mh', ...])

..later

import styled from './my-styled'
export default styled(base('input'))`
  position: relative;
  display: block;
  width: 100%;
`

@ai Sorry I was on my phone. It’s like this

const Link = styled("div", { omitProps: ["foo"] })`...`

<Link foo="bar" />