-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.attrs equivalent #821
Comments
We've discussed this before and we've come to the conclusion that the best way to do this is recommend people use the css prop and attach props just like you would any other react component.(the reasons are described in the linked comment) |
Thanks. That's the first thing I tried though there is a gotcha in that the component becomes stateless so can't have refs. This is an issue for libraries which use refs to attach behaviour to elements (react-dnd, react-measure, etc.) I imagine you could use forwardRef these days but it then starts looking a lot like the withProps code above |
@jamiewinder what's the gotcha with ref regarding defaultProps (which |
Stateless components can't have refs, so while a standard You can if you use For example, if I have a styled If I use the approach suggested above, then I suddenly can't use In styled-components, I can simply: const Column = styled(Flex).attrs({ direction: 'column' })`
color: red;
`; Emotion is already effectively tying props to styles and when components extend other components and may want to define default props (ergo default styles) you'll want to make this as straightforward as possible without the visual noise It just seems to be a logical extension of the emotion's "style as a function of state" tagline - default props = default styles when you're extending one styled component from another. |
Ok, so I'm not sure if the const Column = styled(Flex)`
color: red;
`;
Column.defaultProps = { direction: 'column' } |
Thanks, I think that's my preferred method so far. |
I think the method mentioned by @Andarist just before are such as good as the .attr() way? and also more React familiar. |
I'll go with @Andarist's solution. This is the best solution as it makes sense to add |
The preferred way of doing this is just using EDIT:// Oh, and I would not recommend using |
I'm not clear on what the recommended alternative is. Can you clarify @Andarist, or is there some link to a relevant doc? This issue was the best info I found on achieving this end. Turns out it was a bad week to implement |
You can just not use styled API at all, but if you really want to you can make hoops like this - https://codesandbox.io/s/emotion-uj5z9 import styled from "@emotion/styled";
const customStyled = (tag, options) => {
const styledFactory = styled(tag, options);
return function() {
const Styled = styledFactory.apply(null, arguments);
Styled.attrs = extraProps => {
const WithAttrs = props => {
return <Styled {...extraProps} {...props} />;
};
for (let key in Styled) {
WithAttrs[key] = Styled[key];
}
Object.defineProperty(WithAttrs, "toString", { value: Styled.toString });
return WithAttrs;
};
return Styled;
};
}; |
I'm trying to understand... the proposed solution is to implement the feature ourselves? How does this plays with the Babel Macro version? Can I just import |
We believe that this particular feature is not worth including in the core. We need to think about APIs we provide and can't just satisfy each possible feature request. Also please see this comment - #617 (comment) .
Unfortunately not. |
Also, regular React-way of composition works for this just OK. const Input = styled.input``
const TextInput = props => <Input {...props} type="text" /> Even if this seems like a boilerplate this is just a standard way of composing React component, no special APIs, just React and JavaScript. |
I see, to be honest I feel like this is a quite common use case. I will most likely use But if |
The React composition method works, but it definitely seems like a big step backwards from the simplicity of the |
There are multiple ways to achieve this - without needing to put this into the core. There is always a recompose~ approach ( const withAttrs = (Component, attrs) => props => <Component {...attrs} {...props}/> |
Thanks @Andarist! While still not as clean as |
recompose withProps does not send props (attributes) from the HTML element (StyledComponent): const Range = withProps({ type: 'range' })(styled(Input)`
color: blue;
`); <Range min="0" max="1" step="0.01" onChange={handleVolume} value={state.volume} />
|
One thing that isn't noted here is that const MyComponent = styled.div.attrs(props => {
return {
cssWidth: props.width
}
})`
width: ${props => props.cssWidth};
`
MyComponent.propTypes = {
width: PropTypes.number
} Usage without providing a function doesn't necessarily give any value over the defaultProps approach. Having support for the above use case would massively help in migrating from styled-components to emotion. |
Is there any way to do what @Andarist suggested with the object notation? const Column = styled(Flex, { color: 'red' }); This doesn't seem to work. |
@verekia I'm not sure what you are requesting here, but the used syntax is wrong - take a look like how this kind of thing should be written: https://codesandbox.io/s/xenodochial-http-ydqkx |
Thank you very much! It works well :) Maybe it would be useful to add this example to the Styled documentation. There is no example with this syntax: |
PRs are welcome :) |
Any chance this issue will be re-examined? But it is too verbose and creates a useless wrapper object.
This also requires also importing React just for composing the component with attributes.. |
@lior-chervinsky Would this work for you? const StyledButton = styled.button``;
StyledButton.defaultProps = { type: 'button' }; |
@joemaffei What about when you want if you want to pick an attribute value based on a condition or a prop?
I know that syntax is junk, but does it get my question across? |
Recently I got a little bit more inclined to revisit this. If somebody wants to implement this with proper TS support - let me know and let's discuss this in a new issue (don't jump straight to coding!) |
@remy90 I suggested |
You could write a more complex wrapper which accomplishes this, e.g. import { useTheme } from '@emotion/styled';
export const withAttrs =
(Component, fn) =>
(props) => {
const theme = useTheme();
const attrs = fn({ theme, props });
return <Component {...props} {...attrs} />;
}; and then: const StyledCard = withAttrs(styled(Card)`
display: flex;
flex-direction: column;
`, ({props, theme}) => ({
color: props.active ? theme.linkColor.text : 'transparent',
})) |
I've been trying to solve this issue with a HOC and Typescript for sometime now, but found it to be the "wrong" approach. The main reason is that I have not found a way to infer from the component props' attributes that must become optional after setting some statics. Since I believe this must be an issue for other folks as well, I though it would be nice to share my workaround and also to ask if anyone has solved the issue presented in the last paragraph in a more generic and sophisticated way. // File: utils/types.ts
export type MakeOptional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; // File: input.styles.tsx
// ...
const ClearIcon = styled(Icon)`
align-self: center;
height: 30px;
width: 30px;
`;
const clearIconStyle = css`
stroke: red;
`;
export const Styles = {
ClearIcon: (props: MakeOptional<IconTypes.Props, 'name'>) => (
<ClearIcon {...props}
name={IconTypes.Name.Close}
iconStyle={clearIconStyle}
/>
),
// ...
}; // File: input.tsx
// ...
export const Input: FC<InputTypes.Props> = props => (
<Styles.Wrapper style={props.style}>
// ...
{props.clearable && (
<Styles.ClearIcon />
)}
// ...
</Styles.Wrapper>
); If anyone knows a better way to address the issue, please respond to this comment. |
@emilianomon I was facing the same issue, here is what I came up with: type Defaultize<P extends {}, Q extends Partial<P>> = Omit<P, keyof Q> &
Partial<Pick<P, keyof Q & keyof P>>;
export function attrs<P extends {}, Q extends Partial<P>>(
propsFactory: Q | ((props: Defaultize<P, Q>) => Q),
Component: ComponentType<P>
) {
return forwardRef<ComponentRef<ComponentType<P>>, Defaultize<P, Q>>(
(props, ref) => {
return (
<Component
{...(props as P)}
{...(isFunction(propsFactory)
? propsFactory(props)
: propsFactory)}
ref={ref}
/>
);
}
);
} A bit hacky but it seems to work: type Props = {
foo: number;
bar: boolean;
baz?: string;
};
const A = styled((_: Props) => <div />)`
color: red;
`;
const B = attrs({}, A);
const C = attrs({ foo: 45 }, A);
const D = attrs({ bar: true }, C);
const E = attrs({ foo: 45, bar: true }, A);
const a = <A />; // Error, "foo" and "bar" missing
const b = <B />; // Error, "foo" and "bar" missing
const c = <C />; // Error, "bar" missing
const d = <D />; // Ok
const e = <E />; // Ok Something like this might also work (not tested on my side). |
This was mentioned in #109 just over a year ago but with very little discussion around it so I hope you don't mind me raising it again.
I've been toying with the idea of switching a very large project over from styled-components to emotion for a while now. Today I bit the bullet and tried doing a mass conversion of thousands of components.
I was very pleased and surprised with how straightforward this was to do thanks to your similar APIs. However, the one feature I'm sorely missing is the ability to define default props; .attrs.
Again, in #109 this was raised but it looks like the suggested approach is to use recompose and
withProps
. Personally I think this would be better as part of the library.Much of my conversion was a simple find and replace job, but converting .attrs required a comparatively disproportionate amount of effort. Having to depend on another library for the purpose of importing a single function is fine I suppose, but the syntax is also a bit jarring. The nice thing about the
styled
API is how readable it can be... however when a styled component with some logical defaults comes along it takes a bit more mental parsing than seems necessary.Compare
or
with
I know much of this is personal preference, but the withProps variation looks so unlike a 'normal' styled component yet the act of specifying some sensible default props isn't really that exotic, is it?
Anyway, just thought I'd put this out there. I know there is personal preference and library 'purity' as factors too. It just seems to me this would be a tiny addition but one which may help fellow wannabe styled components users like me take the leap.
Thanks.
The text was updated successfully, but these errors were encountered: