emotion
emotion copied to clipboard
styled-components withComponent deprecation - Emotions stance?
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 😄
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 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?
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?
@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)
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.
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?
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',
})
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 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.
I don't fully understand the latest problem mentioned here - could you prepare a codesandbox that would illustrate the problem?
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 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>
);
}
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.