Skip to content
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

sibling selectors with computed props #743

Closed
ianstormtaylor opened this issue Jun 22, 2018 · 18 comments
Closed

sibling selectors with computed props #743

ianstormtaylor opened this issue Jun 22, 2018 · 18 comments
Labels

Comments

@ianstormtaylor
Copy link

  • emotion version: 9.2.4
  • react version: 16.0.0

Relevant code:

const Container = styled('div')`
  background: ${props => props.active ? 'red' : 'transparent'};

  & + & {
 	margin-top: 20px;
  }
`

Here I'm trying to style the sibling selector for a given component. Without the computed background it works fine, since the CSS classname will always be the same. But as soon as the computed property is added it seems like that means that the classname will change, depending on props.active.

But if the component with props.active is in the middle of two others, this will also break the & + & since it's no longer referring to all of the component instances.


I think the way to solve this would be to have a "computed class" for each component, and also a "base class" that never changes. And the & self selector would always use the base class, so that it's unaffected by computed classes.

This sounds similar to something styled-components added.


Or, I might be doing something wrong, let me know! Thanks.

@tkh44
Copy link
Member

tkh44 commented Jun 25, 2018

This should work if you are using the babel-plugin

const Container = styled('div') `
  background: ${props => props.active ? 'red' : 'transparent'};

  & + ${Container} {
 	margin-top: 20px;
  }
`

@emmatown
Copy link
Member

It should work if you do this. You need a function returning the component since the component hasn't been declared yet when you reference it.

const Container = styled('div') `
  background: ${props => props.active ? 'red' : 'transparent'};

  & + ${() => Container} {
 	margin-top: 20px;
  }
`

@tkh44
Copy link
Member

tkh44 commented Jun 28, 2018

Interesting, what you said makes sense but I tested it on a local app with the Babel plugin on and it worked 🤷

@ianstormtaylor
Copy link
Author

Hey @tkh44 and @mitchellhamilton thanks for the workarounds! As far as I can tell, it would actually then require using the function in both places? Like so...

const Container = styled('div') `
  background: ${props => props.active ? 'red' : 'transparent'};

  ${() => Container} + ${() => Container} {
 	margin-top: 20px;
  }
`

This starts to feel really hacky though.


More generally... this feels like something where the default behavior isn't right. It's really hard for me to think of a case meriting the current behavior, where any change to any of the dynamic props will change the & + & in mind-bending ways.

It seems like & should be pegged to refer to the component itself—regardless of the current values of dynamic properties.

Do you agree? Is this something that can be fixed?

@aaronjensen
Copy link
Contributor

How would I use this with the object syntax? Is that possible?

@tkh44
Copy link
Member

tkh44 commented Jul 13, 2018

I think using a function value and object styles it would look like this. I haven't tested it though.

const Container = styled('div')((props) => ({
 background: props.active ? 'red' : 'transparent',
 [`${Container} + ${Container}`]: {
   marginTop: 20
 }
}))

@stale
Copy link

stale bot commented Sep 11, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Sep 11, 2018
@ianstormtaylor
Copy link
Author

This still feels like an API flaw to me. All of the workarounds are unintuitive (and hard to determine what even the correct one is), whereas the current behavior is almost never going to be what people want in the first place.

@stale stale bot removed the stale label Sep 13, 2018
@Andarist
Copy link
Member

I think you are right, not sure how to reliably generate unique stable class names for scoping (targeting &) purposes without babel plugin (emotion@10 has a goal of babel plugin being totally optional, only providing build-time optimizations and such).

@emmatown
Copy link
Member

emmatown commented Sep 13, 2018

I disagree, I think the behavior here is correct. & has to target the hashed class name otherwise the styles would be the same for all of the component instances. The way I think about it is that every style block is implicitly wrapped in &. As an example, how would it work in this case if & referred to the component selector class name?

let Comp = styled.div`
  &:hover {
    color: ${props => props.hoverColor};
  }
`;

@aaronjensen
Copy link
Contributor

What if a new syntax was introduced like && ? It could mean any variation of this

@Andarist
Copy link
Member

@mitchellhamilton u are right (as always 😅 ), maybe we could somehow detect problematic patterns (with eslint plugin?) and warn about them? The problematic pattern here would be i.e. & + & not followed by a block with dynamic interpolation

@ianstormtaylor
Copy link
Author

@mitchellhamilton that's a great point. What do you think of && (or similar) like @aaronjensen proposed? It seems like there should be a "stable" way to refer to the component's class without the confusing dynamic functions. So maybe the current distinction isn't wrong, but there's still a hole in the API.

@emmatown
Copy link
Member

& is pretty much just replaced with .css-hash so && turns into .css-hash.css-hash so that wouldn't work but tbh I don't really understand why using () => SomeComponent is confusing?

@wongm3
Copy link

wongm3 commented Apr 13, 2019

@mitchellhamilton When I use () => SomeComponent in typescript I'm getting an error

Argument of type 'TemplateStringsArray' is not assignable to parameter of type 'Interpolation<undefined>'.
  Type 'TemplateStringsArray' is not assignable to type 'ObjectInterpolation<undefined>'.
    Types of property 'filter' are incompatible.
      Type '{ <S extends string>(callbackfn: (value: string, index: number, array: readonly string[]) => value is S, thisArg?: any): S[]; (callbackfn: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): string[]; }' is not assignable to type 'string | string[]'.
        Type '{ <S extends string>(callbackfn: (value: string, index: number, array: readonly string[]) => value is S, thisArg?: any): S[]; (callbackfn: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): string[]; }' is missing the following properties from type 'string[]': pop, push, concat, join, and 15 more.

Is there something I'm missing?

@Andarist
Copy link
Member

The issue got stale and I think it's appropriate to close this now. Thank you for your understanding.

@ianstormtaylor
Copy link
Author

What ever happened to the && suggestion? Not sure why @mitchellhamilton says it wouldn't work, seeing as you could just replace && first and replace & after.

Still seems like a footgun in the API to me.

@Andarist
Copy link
Member

There are existing suggestions to use && to overcome specificity issues. If anything this would have to use different token than & as it has well~ established semantics and we wouldn't like to mess with this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants