emotion icon indicating copy to clipboard operation
emotion copied to clipboard

styled-components withComponent deprecation - Emotions stance?

Open MMT-LD opened this issue 4 years ago • 13 comments

Hey,

So this is not really a feature more of a question that i didn't really know where to file it under - sorry if this is the wrong place.

I see that styled components is looking at deprecating .withComponent, however what is emotions stance on this? For me the positive of withComponent is mainly when using typescript to prevent issues with the 'as' component where it tries to dynamically account for what element has been passed in. Also, i have heard stories of slow performance when using 'as' prop in IDE's. Maybe .withComponent helps with this?

for reference: https://styled-components.com/docs/api#withcomponent

Thanks in advance 😄

MMT-LD avatar Sep 14 '20 08:09 MMT-LD

We don't plan to remove it right now but we think it's a really quirky API and that you should avoid using it. It's much better to compose things in a top-down manner. Instead of doing this:

const Anchor = styled.a`color: hotpink;`
const Span = Anchor.withComponent('span')

you can always do this:

const styles = css`color: hotpink;`

const Anchor = styled.a(styles)
const Span = styled.span(styles)

Andarist avatar Sep 14 '20 09:09 Andarist

@Andarist Thanks for the clarification. Just wandering if we should notify users that its probably worth avoiding and using the as prop or the approach above?

MMT-LD avatar Sep 15 '20 11:09 MMT-LD

I'm trying to come up with a clean solution to replace the usage of withComponent which since v11 seems to no longer replace the types of onXYZ events (and many other things I would assume). My current example would be this:

// Flex
const Flex = styled.div</* styled-system props */>`${compose(/* styled-system css */)}`;

// Form
const Form = styled(Flex)``.withComponent('form');

// Usage
<Form onSubmit{e => /* ... */} >
// type of onSubmit is (((event: React.FormEvent<HTMLDivElement>) => void) & ((event: React.FormEvent<HTMLFormElement>) => void)) | undefined

So this seems like withComponent no longer replaces types of attributes like onSubmit but rather adds them which breaks all of my usages.

The question is, what are my options? The suggested solution of just taking the styles out would make it really unbearable to have additional export for each and every styled component I want to extend and also it's props. The above example looks ok but mine would look something like this, plus I would have to do some hacky stuff to be able to use compose in css

// Flex
export type FlexProps = /* styled-system props */;
export const FlexCss = (p: any) => css`${compose(/* styled-system css */)(p)}`;
const Flex = styled.div<FlexProps>`${p => FlexCss(p)}`;

// Form
import { FlexProps, FlexCss } from ".";
const Form = styled.form<FlexProps>`${FlexCss}`;

Can anyone suggest a better way?

Haaxor1689 avatar Nov 20 '20 20:11 Haaxor1689

@Andarist what about for react router link. would this be the same?

const Button = styled.button(styles)
const ButtonLink = styled(Link)(styles)
const ButtonAnchor = styled.a(styles)

theskillwithin avatar Dec 08 '20 22:12 theskillwithin

Just encountered the same problem as @Haaxor1689. Any solutions yet?

Migrating a large code base from styled-components has been painful to say the least. The lack of a truly polymorphic as-prop makes the library so much less flexible to work with and the withComponent typings seem broken.

skrivanos avatar Mar 31 '21 19:03 skrivanos

So... The problem I run into is styling React's Link component from react-router-dom when using TypeScript. withComponent fixes this for me... What will I do when this is deprecated?

hewseph avatar Feb 07 '22 16:02 hewseph

We don't plan to remove it right now but we think it's a really quirky API and that you should avoid using it. It's much better to compose things in a top-down manner. Instead of doing this:

const Anchor = styled.a`color: hotpink;`
const Span = Anchor.withComponent('span')

you can always do this:

const styles = css`color: hotpink;`

const Anchor = styled.a(styles)
const Span = styled.span(styles)

How do I do this with dynamic styles that every referencing styled component would use?

const DefaultText = styled.span<{ secondary?: boolean,}>(props => ({
   color: prop.secondary ? "red" : "green"
))}

export const H1 = styled(DefaultText.withComponent('h1'))({
    fontSize: '2rem',
})

export const P = styled(DefaultText.withComponent('p'))({
    fontSize: '0.6rem',
})

hewseph avatar Feb 07 '22 16:02 hewseph

Fwiw, I use this via MUI a lot, and even in custom components - a lot of the time you want to render a custom component with a specific HTML element. E.g., <Typography component='span'>Some text</Typography>.

When we use styled() for some better css reusability:

const StyledType = styled(Typography)(sx({ color: 'gold' }));

we no longer have the ability to specify a child html component, so the withComponent() is really graceful:

const StyledType = styled(Typography)(sx({ color: 'gold' })).withComponent('span');

Not sure if any of the proposed solutions truly cover this use case?

brandonscript avatar Aug 19 '22 20:08 brandonscript

@brandonscript This is the exact issue I've been scouring the internet for. Emotion has not proposed a solution for wrapping components(like Typography) and keeping their base props like "component". as is dynamic so its just not the same.

Alcas1 avatar Aug 19 '22 21:08 Alcas1

I don't fully understand the latest problem mentioned here - could you prepare a codesandbox that would illustrate the problem?

Andarist avatar Aug 20 '22 08:08 Andarist

Oh, I don't even need to put it in a code sandbox – take for example:

import { Box } from "@mui/material";

const MyBox = styled(Box)(sx({ bgcolor: "red" }));

The custom component MyBox is always going to be a div because that's what MUI's default component is set.

What if I wanted the box to be an aside or a section? The only way to do that today is:

const MyBox = styled(Box)(sx({ bgcolor: "red" }));
MyBox.withComponent('aside');`

which works brilliantly. I'd be down for a syntax like this though:

const MyBox = styled(Box, { 
  as: "aside"
})(sx({ bgcolor: "red" }));

brandonscript avatar Aug 20 '22 17:08 brandonscript

@brandonscript Using MUI, below are some solutions/variations I commonly use that you can elaborate on. Sometimes MUI types will be clobbered because the style wrapper can't infer MUI's complex types, but there's usually a low-footprint way around it. Typography and Autocomplete components usually give me the most hassle.

https://codesandbox.io/s/old-cloud-p8fw0h?file=/src/App.tsx

import styled from "@emotion/styled";
import { Box, Typography, TypographyProps } from "@material-ui/core";
import "./styles.css";

const StyledBox = styled(Box)({
  width: 50,
  height: 50,
  backgroundColor: "red"
});

const StyledType1 = styled(Typography)<
  TypographyProps<"div", { component: "div" | "aside" | "span" }>
>({ color: "gold" });

const StyledType2 = styled((props: TypographyProps) => (
  <Typography component="aside" {...props} />
))({ color: "gold" });

export default function App() {
  return (
    <div className="App">
      <StyledBox component={"aside"} />
      <StyledBox component={"p"} />

      <StyledType1 component={"aside"}>Styled Type</StyledType1>
      <StyledType1 component={"span"}>Styled Type</StyledType1>

      <StyledType2>Styled Type</StyledType2>
    </div>
  );
}

jmca avatar Aug 20 '22 23:08 jmca

That certainly works and I've done that, but at that point why not just make a component and pass in sx? Also some unfortunate ref forwarding that gets lost sometimes. Not that it's a problem, just that it's nice to have a semantically elegant way to do it.

brandonscript avatar Aug 21 '22 04:08 brandonscript