60

I have a styled component that is extending a third-party component:

import Resizable from 're-resizable';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
    flex: 0 0 ${(props) => props.someProp}px;
`;


const PaneContainer = ({ children, someProp }) => (
    <StyledPaneContainer
        someProp={someProp}
    >
        {children}
    </StyledPaneContainer>
);

export default PaneContainer;

This resulted in the following error in the browser console:

Warning: React does not recognize the someProp prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase someProp instead. If you accidentally passed it from a parent component, remove it from the DOM element

So, I changed the prop to have a data-* attribute name:

import Resizable from 're-resizable';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
    flex: 0 0 ${(props) => props['data-s']}px;
`;


const PaneContainer = ({ children, someProp }) => (
    <StyledPaneContainer
        data-s={someProp}
    >
        {children}
    </StyledPaneContainer>
);

export default PaneContainer;

This works, but I was wondering if there was a native way to use props in the styled component without them being passed down to the DOM element (?)

JoeTidee
  • 24,754
  • 25
  • 104
  • 149

5 Answers5

89

2020 UPDATE: Use transient props

With the release 5.1.0 you can use transient props. This way you do not need an extra wrapper i.e. unnecessary code is reduced:

Transient props are a new pattern to pass props that are explicitly consumed only by styled components and are not meant to be passed down to deeper component layers. Here's how you use them:

const Comp = styled.div`
  color: ${props => props.$fg || 'black'};
`;

render(<Comp $fg="red">I'm red!</Comp>);

Note the dollar sign ($) prefix on the prop; this marks it as transient and styled-components knows not to add it to the rendered DOM element or pass it further down the component hierarchy.

The new answer should be:

styled component:

const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
    flex: 0 0 ${(props) => props.$someProp}px;
`;

parent component:

const PaneContainer = ({ children, someProp }) => (
    <StyledPaneContainer
        $someProp={someProp}  // '$' signals the transient prop        
    >
        {children}
    </StyledPaneContainer>
);
Vandesh
  • 6,368
  • 1
  • 26
  • 38
EliteRaceElephant
  • 7,744
  • 4
  • 47
  • 62
49

As suggested by vdanchenkov on this styled-components github issue you can destructure the props and only pass the relevant ones to Resizable

const ResizableSC = styled(({ someProp, ...rest }) => <Resizable {...rest} />)`
    flex: 0 0 ${(props) => props.someProp}px;
`;
tskjetne
  • 2,264
  • 1
  • 8
  • 10
12

For those (like me) that landed here because of an issue with SC and react-router's Link.

This is basically the same answer as @tskjetne, but with JS syntax style.

const StyledLink = styled(({ isCurrent, ...rest }) => <Link {...rest} />)(
  ({ isCurrent }) => ({
    borderBottomColor: isCurrent ? 'green' : 'transparent',
  }),
)
Fellow Stranger
  • 32,129
  • 35
  • 168
  • 232
2

Starting with version 5.1, you can use shouldForwardProp configuration property:

This is a more dynamic, granular filtering mechanism than transient props. It's handy in situations where multiple higher-order components are being composed together and happen to share the same prop name. shouldForwardProp works much like the predicate callback of Array.filter. A prop that fails the test isn't passed down to underlying components, just like a transient prop.

Keep in mind that, as in this example, other chainable methods should always be executed after .withConfig.

I find it much cleaner than transient props, because you don't have to rename a property, and you become explicit about your intentions:

const ResizableSC = styled(Resizable).withConfig({
  // Filter out the props to not be shown in DOM.
  shouldForwardProp: (prop, defaultValidatorFn) =>
    prop !== 'someProp'
    && defaultValidatorFn(prop),
})`
  flex: 0 0 ${(props) => props.someProp}px;
`;

This is especially handy if you are using TypeScript and sharing the same props type both in your main component and the corresponding styled component:

import { HTMLAttributes } from 'react';
import styled from 'styled-components';

// Props type.
type CaptionProps = HTMLAttributes<HTMLParagraphElement> & {
  size: number,
};

// Main component.
export const CaptionStyles = styled('p').withConfig<CaptionProps>({
  // Filter out the props to not be shown in DOM.
  shouldForwardProp: (prop, defaultValidatorFn) => (
    prop !== 'size'
    && defaultValidatorFn(prop)
  ),
})`
  flex: 0 0 ${(props) => props.size}px;
`;

// Corresponding styled component.
export function Caption({ size }: CaptionProps) {
  return (
    <CaptionStyles size={size} />
  );
}

shouldForwardProp API reference

Vasiliy Artamonov
  • 939
  • 2
  • 8
  • 24
-1

You can try with defaultProps:

import Resizable from 're-resizable';
import PropTypes from 'prop-types';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
    flex: 0 0 ${(props) => props.someProp}px;
`;

StyledPaneContainer.defaultProps = { someProp: 1 }

const PaneContainer = ({ children }) => (
    <StyledPaneContainer>
        {children}
    </StyledPaneContainer>
);

export default PaneContainer;

We can also pass props using 'attrs'. This helps in attaching additional props (Example taken from styled components official doc):

const Input = styled.input.attrs({
  // we can define static props
  type: 'password',

  // or we can define dynamic ones
  margin: props => props.size || '1em',
  padding: props => props.size || '1em'
})`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;

  /* here we use the dynamically computed props */
  margin: ${props => props.margin};
  padding: ${props => props.padding};
`;

render(
  <div>
    <Input placeholder="A small text input" size="1em" />
    <br />
    <Input placeholder="A bigger text input" size="2em" />
  </div>
); 
  • Could you explain this answer? – JoeTidee Apr 14 '18 at 20:17
  • You can pass default props to the component StyledPaneContainer using defaultProps. Did you try this out? – Chasing Unicorn - Anshu Apr 14 '18 at 20:20
  • No, I wanted to understand why `defaultProps` was suggested if the value of `someProp` needs to be dynamic (?) – JoeTidee Apr 15 '18 at 18:32
  • In that we can try with 'attrs'. Updating my answer. Please check, let me know your thoughts on the same. – Chasing Unicorn - Anshu Apr 15 '18 at 18:41
  • Unfortunately `attrs` passes all props through to the DOM, so it doesn't prevent React's invalid props warning unless you're only using prop names that also happen to be valid HTML attributes. I hope styled-components improves its API in the future to better support this use case. In the meantime @tskjetne's answer is a viable workaround for most cases. – Matt Browne May 24 '18 at 15:37