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

RFC: Component Selector #142

Closed
mxstbr opened this Issue Oct 27, 2016 · 25 comments

Comments

Projects
None yet
@mxstbr
Member

mxstbr commented Oct 27, 2016

I was chatting to @markdalgleish at Reactive conf and he had this great idea to implement a other-styled-component-selector.

The API would look like this:

const Button = styled.button`some: styles;`

const Box = styled.box`
  > ${Button} {
    // A button component nested within a box will have these styles
    other: styles;
  }
`

This would allow you to override styles of children, and from children react to parents, too! What do you think?

@geelen

This comment has been minimized.

Member

geelen commented Oct 27, 2016

I like it.

@stephenkingsley

This comment has been minimized.

Contributor

stephenkingsley commented Oct 29, 2016

just override the Button within a box, not change the Button?

I like it

@jonaskello

This comment has been minimized.

jonaskello commented Oct 30, 2016

I have some "complex" reusable components in a library. With "complex" I mean they contain multiple <select>, <input> etc. I would like importers of these components to be able to override each part of the complex component (eg. override the styles of one of the <select> inside the component). This suggestion seems like it would fit my needs. Just wanted to check if there is some way to handle this type of "complex" components already or is styled-component only usable for primitives like <Button>, <Input> etc at the moment?

@geelen

This comment has been minimized.

Member

geelen commented Oct 31, 2016

After a bit more thought I... don't think this is really going to be possible. Or at least, easy enough to justify its addition.

Consider this:

const X = styled.div`
  one: rule;
  ${ props => props.alt && 'then: another;' }
`

const Y = styled.div`
  > ${ X } {
    over: ride;
  }
`

If you construct a <X/> you get <div className='random-hash-123'/>, whereas if you construct a <X alt/> you get <div className='random-hash=456'/>. So you can't use X's generated classnames inside Y's rules.

You'd have to generate a globally unique classname that was attached to all instances of X that applied no rules by itself. But then, how do you generate that classname? Elsewhere we hash the contents of the CSS to produce a deterministic, order-independent hashed classname. Order-independence is important because we don't want the order of import statements in a project to affect the generated classnames. That's particularly important in a server-side rendered context where the generated classnames need to match on the client.

So, if someone can come up with a deterministic, order-independent random classname generator that can distinguish between const X = styled.div and const Y = styled.div we can do this. Otherwise, we'll have to drop the idea.

Also, any solution can't depend on running a babel transform or other build step. You can do cool things with babel transforms I just don't want to need them for anything in this project. Basically, my threshold is that styled-components needs to be able to work with Create React App without ejecting.

@AsaAyers

This comment has been minimized.

AsaAyers commented Nov 3, 2016

Would it be possible to hash just the static portions of the styles to get a deterministic name? If your hashing mechanism is just operating on strings and can hash invalid CSS, it seems like this should work.

When processing the tagged template literal, you replace all of the dynamic things with a placeholder:

const XClassName = hashOf(`
  one: rule;
  /* generic placeholder */
`)

const YClassName = hashOf(`
  > /* generic placeholder */ {
    over: ride;
  }
`)

Any components that don't have dynamic values would end up generating the same hash. This could be solved by adding a static prefix or suffix before hashing.

@haikyuu

This comment has been minimized.

Contributor

haikyuu commented Nov 13, 2016

I propose the following API. Since we have components now, i think that every component should only styles itself and its direct children like a good and happy family.

const StyledDad = styled.View`
align-items: flex-start;
`
StyledDad.childrenStylist(child, index, children, parentProps, theme)=> {
  if(child.props.chosenOne){
    return 'align-self: center'
    // merge this with the child styles
 }
}

Bonus: this would allow to separate layout and move it to the parent.

@srosset81

This comment has been minimized.

srosset81 commented Nov 17, 2016

Sub-components selection is a feature that I find is really missing from styled-component. It makes me stick to CSS modules for some of my, more complex, components. However using two styling technologies is in my opinion a real problem...

@geelen

This comment has been minimized.

Member

geelen commented Nov 18, 2016

@srosset81 can you post some examples of what you want to do with sub-component selectors? I get that they're useful but I do think there's usually a better alternative for most cases.

@srosset81

This comment has been minimized.

srosset81 commented Nov 18, 2016

@geelen For example if you want to change the style of a sub-component when the mouse goes over its parent. This is a common pattern for CSS effects. If there is just one child, you can do a "> div" but it's not possible if there are many different children.

.parent-component-class {
  .child-component-class {
    opacity: 0;
  }
  &:hover {
    .child-component-class {
      opacity: 1;
    }
  }
}
@srosset81

This comment has been minimized.

srosset81 commented Nov 18, 2016

@geelen The way around this limitation is to give an ID to the component.

<RemoveButton id="RemoveButton"/>

How about giving the possibility to auto-generate this ID for every styled component? This way the hashed class names would stay unique, but it would be dead easy to select sub-components.

@alexanbj

This comment has been minimized.

alexanbj commented Nov 18, 2016

It would be really cool with some kind of official way to do this.

Right now I accomplish this by adding my own CSS class (in addition to the one generated by styled-components) to the children. I then use that class in the parent as a selector to apply different styles to the children based on the props that the parent receives. This feels really hackish though.

@jzimmek

This comment has been minimized.

jzimmek commented Dec 15, 2016

I really appreciate the work of the styled-components team, but this issue popped up shortly after we decided to use styled-components in our project.

Without any kind of component selector we see ourself either:

  • break component encapsulation by using html tag/class selectors in the parent component css
  • or have to introduce a bunch of intermediate styled-components, even for minimal adjustments like setting a margin

So we started to look into a possible solution. With/Without babel does not matter to us, because we have an existing webpack/babel build-chain anyways.

We created our own babel plugin:

babel-plugin-styled-components-selector

It is still very early stage, but already works for various use-cases and allows you to use the following syntax:

const Button = styled.button`
  color: black;
`;
const Container = styled.div`
  > ${Button.selector} {
    color: green;
  }
`;

See the README for more examples.

PS:
I would love to contribute a pull request to the styled-components core to support an even cleaner syntax by hiding the ".selector" attribute. As of 1.1.3 a function as interpolation value is always invoked. Honoring an existing ".selector" (or some more unique attribute name) inside the interpolation logic would allow the following syntax:

const Button = styled.button`
  color: black;
`;
const Container = styled.div`
  > ${Button} {
    color: green;
  }
`;

I would love to continue the discussion in this thread on this. Even if this is no solution for the core of styled-components, you are very welcome to use and contribute to it.

@mxstbr

This comment has been minimized.

Member

mxstbr commented Dec 15, 2016

@jzimmek just need to land #227 then there'll be native support for this 😉 trying to figure out how to properly do it, appreciate the intermediary solution though, that's a pretty smart workaround!

@jzimmek

This comment has been minimized.

jzimmek commented Dec 16, 2016

@mxstbr Great! Really looking forward to have an out-of-the-box solution inside core for this!

I came across the use-case of having one/multiple intermediate "non-styled" components inside the component hierarchy.

Here a contrived example:

let Button = styled.button`
  color: red;
`

let ButtonWithLoadingIndicator = React.createClass({
  render(){
    return <div>
      <Button>Click me</Button>
      <img/>
    </div>
  }
})

let Box = styled.div`
  background: #c0c0c0;

  // this does not work because not a styled-component
  > ${ButtonWithLoadingIndicator} {
    color: #fff;
  }

  // this does not work because there is an intermediate html <div> element in between
  > ${Button} {
    color: #fff;
  }

  // this will work but potentially let the css cascade to all nested <Button/>
  ${Button} {
    color: #fff;
  }

  // this will work but will again break encapsulation by assuming a "div" in the nested component <ButtonWithLoadingIndicator/>
  > div > ${Button} {
    color: #fff;
  }
`

render(){
  return <Box>
    <ButtonWithLoadingIndicator/>
  </Box>
}

I am not sure if this can really be solved in a totally generic and automated way - without manually setting classes etc.

Have you any thoughts on this?

Btw. I also enhanced the babel-plugin-styled-components-selector plugin.

It will support the originally purposed syntax now:

const Button = styled.button`
  color: black;
`;
const Container = styled.div`
  > ${Button} {
    color: green;
  }
`;

The ${Button.select} can be written as ${Button} now. Works with the vanilla version of styled-components.

@protoEvangelion

This comment has been minimized.

protoEvangelion commented Dec 22, 2016

@mxstbr @jzimmek This would be awesome! I can see a few areas where this would come in quite handy 😄

@SachaG

This comment has been minimized.

Contributor

SachaG commented Jan 4, 2017

I was actually watching @geelen's talk, which recommends this very pattern (styling a child from a parent) for flexbox layouts, if I'm not mistaken.

I agree it'd be great to have an official way to do it with styled-components.

@mxstbr mxstbr modified the milestone: v2.0 Jan 17, 2017

@mxstbr

This comment has been minimized.

Member

mxstbr commented Jan 18, 2017

As of landing #227 in the v2 branch, this is now possible. 🎉 As soon as v2 lands on npm you'll be able to use it.

I'll close this issue so we don't clutter our milestone, please try the v2 branch on your projects and let us know if it breaks.

@mxstbr mxstbr closed this Jan 18, 2017

@wmertens

This comment has been minimized.

Contributor

wmertens commented Jan 31, 2017

I am not convinced that styling children from the parent is something to encourage.

In any case, when it is unavoidable, like when you are making a layout component based on flexbox, I do something like

const Layout = styled.div`
  display: flex;
  & > * {
    flex: 1;
  }
`

The child selectors are also very useful for these purposes.

@mxstbr

This comment has been minimized.

Member

mxstbr commented Jan 31, 2017

I am not convinced that styling children from the parent is something to encourage.

It's not something we will encourage, but that doesn't mean the possibility shouldn't be there. The same thing applies to nesting, I try to avoid nesting as much as possible and we could've made it impossible in styled-components, but it does have it fringe use cases and we want to cover all the ground.

@Tim152

This comment has been minimized.

Tim152 commented Feb 8, 2017

just do it!

@jenyckee

This comment has been minimized.

jenyckee commented Nov 13, 2017

I am not convinced that styling children from the parent is something to encourage.

Could you elaborate on why this is the case?

@kitten

This comment has been minimized.

Member

kitten commented Nov 13, 2017

@jenyckee this is something we can elaborate on in the docs potentially (issues and PRs welcome 😄) but this issue might not be the right place to discuss it (Closed in January ! 😅)

Check out what we're recommending on the site/docs right now for this pattern: https://www.styled-components.com/docs/faqs#can-i-refer-to-other-components

We could have nested the [rule within the parent], but then we'd have to consider both sets of rules to understand why Icon behaves as it does.

So it's about encapsulation. We always recommend to strive to keep the styling for one component in one place, meaning with that component. This way you'll always immediately know where the styling is coming from: From that component. What sounds simple is actually an easy way to scale a project to a large team and codebase.

@mbrowne

This comment has been minimized.

mbrowne commented Feb 8, 2018

@kitten The "can-i-refer-to-other-components" question seems to have been removed from the FAQ. Is that because the recommended practice has changed?

@probablyup

This comment has been minimized.

Contributor

probablyup commented Feb 8, 2018

@mbrowne

This comment has been minimized.

mbrowne commented Feb 8, 2018

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment