Giraphi
Giraphi

Reputation: 1651

styled-components: Extend existing component with additional prop for styling

I'm using styled-components to override styling from an existing component, in this case ToggleButton from material ui. However I want my new component to have one additional property (hasMargin) that controls the style:

import {ToggleButton} from "@material-ui/lab";
import styled, {css} from "styled-components";

const StyledToggleButton = styled(ToggleButton)<{ hasMargin?: boolean }>`
  && {
    color: red;

    ${props => props.hasMargin &&
      css`
          margin: 10px;
      `}
   }
`;

My intention is that StyledToggleButton behaves exactly like ToggleButton but has red color and can be called with an additional property hasMargin to turn on the margin css. I want to call it like this:

    <StyledToggleButton
      value={""} // ToggleButton prop
      size={"small"} // ToggleButton prop
      hasMargin={true} // My prop from StyledToggleButton
    >
      click
    </StyledToggleButton>

But if I do it like this, in the browser console I get:

Warning: React does not recognize the `hasMargin` prop on a DOM element.

This is because StyledToggleButton seems to also pass hasMargin further to ToggleButton and ToggleButton of course can't and shouldn't deal with that (passing it down to the DOM element where it makes no sense). What is the best way to rewrite my code, so hasMargin is only processed by StyledToggleButton?

I've made a codesandbox to illustrate the issue. Thank you!

Upvotes: 16

Views: 18083

Answers (2)

Aron
Aron

Reputation: 9248

This is exactly what transient props are for.

All you need to do is prefix the prop with a $ and styled-components won't pass the prop to the underlying DOM element.

// Note the $ prefix in the prop name             👇
const StyledToggleButton = styled(ToggleButton)<{ $hasMargin?: boolean }>`
  && {
    color: red;

    // Just use the prop as normal
    ${(props) =>
      props.$hasMargin &&
      css`
        margin: 10px;
      `}
  }
`;

<StyledToggleButton
  value={""}
  size={"small"}
  {/* Assign the prop as normal */}
  $hasMargin={true}
>
  click
</StyledToggleButton>;

Here's your updated Codesandbox link with that error gone.

Upvotes: 28

Konstantin Samarin
Konstantin Samarin

Reputation: 867

What about wrapping the ToggleButton with span and applying styles from there?

const StyledToggleButton = styled.span<{ hasMargin?: boolean }>`
  margin: ${props => props.hasMargin && '50px'};
  
  button {
    color: red !important;
  }  
`

interface MyButtonProps extends ToggleButtonProps {
  hasMargin?: boolean;
}

function MyButton(props: MyButtonProps) {
  const { hasMargin, ...toggleButtonProps } = props;

  return (
    <StyledToggleButton hasMargin={hasMargin} >
      <ToggleButton {...toggleButtonProps}>
      {props.children}
      </ToggleButton>
    </StyledToggleButton>
  );
}

export default function App() {
  return (
    <div className="App">
      <MyButton value={""}>click</MyButton>

      <MyButton value={""} size={"small"} hasMargin={true}>
        click
      </MyButton>
    </div>
  );
}

Upvotes: 0

Related Questions