
Component namespacing with Emotion
Naming, naming, naming. Oh god. One of the hardest part in computer science. Fortunately, we've got some conventions about that which help us not to overthink every time we must name something.
For instance, there's one: BEM.
BEM
I'm a BEM lover for ages. The first time I saw this method, I understood quickly it was a really great way to avoid conflict between elements and - IMO - to remove all the painful part of CSS. (You've got a good article about this on css-tricks.com).
Before using CSS Modules or styled-components or Emotion, my stylesheet code was something like that:
1.MyBlock {2 [...]3}45.MyBlock-myElement {6 [...]7}89.MyBlock-myElement--myModifier {10 [...]11}
It is not exactly the ✌️ official ✌️ convention (you've got different ones in fact) but for some reasons like readability or double-clicking on name to select, this was the convention I used and it worked really well!
But this is a different time and now I mostly use Emotion to create my styled components.
Emotion
So let's begin about how to namespace your components.
Here is a basic example:
1import styled from "@emotion/styled"23const MyStyledComponent = styled.div``
Alright, now let's imagine now we've got two components: one parent, one child.
The classic way could be:
1const Parent = styled.div``23const Child = styled.div``
We want now to adjust Child
depending on triggering a hover on Parent
.
We can write this:
1const Parent = styled.div`2 &:hover ${Child} {3 color: red;4 }5`67const Child = styled.div``
Great.
But as a BEM fan, I rather prefer to write my code like this:
1const MyBlock = styled.div`2 &:hover ${MyBlockMyElement} {3 color: red;4 }5`67const MyBlockMyElement = styled.div``
This is cool because you know that your element depends on a block. But when you want to export, you'll get something like:
1// my-component.js2export const MyBlock = styled.div`3 &:hover ${MyBlockMyElement} {4 color: red;5 }6`78export const MyBlockMyElement = styled.div``
1// app.js2import { MyBlock, MyBlockMyElement } from "./my-component"
Nope. It is unpleasant to read now.
A solution could be to create a namespace by using objects.
1// my-component.js2const MyComponent = styled.div``34MyComponent.MyElement = styled.div``56export default MyBlock
which is great because you can do:
1// app.js2import MyComponent from "./my-component"34const MyApp = () => (5 <MyComponent>6 <MyComponent.MyElement />7 </MyComponent>8)
Elegant, no?
This is my favourite way to write namespaced components. 🙌🏻
Oh noes!
But, there's a but. 😬
Did you remember when we added a :hover
trigger? Let's try with this convention:
1// my-component.js2const MyComponent = styled.div`3 &:hover ${MyComponent.MyElement} {4 color: red;5 }6`78MyComponent.MyElement = styled.div``910export default MyBlock
By doing this, you'll get this error:
1🙅♂️ can't access property "MyElement", MyComponent is undefined
Well, it is quite obvious as at this moment, MyComponent
doesn't exist yet.
🗣 So you told us to use namespacing via objects but it seems that it creates some issues now!.
Yes, that's right but I still find that this convention is elegant and I want to go deeper with it. So how could we do?
The trick
Well, after looking into the github issues of emotion, I found a solution, it is just to create an anonymous function which returns the child!
1// my-component.js2const MyComponent = styled.div`3 &:hover ${() => MyComponent.MyElement} {4 color: red;5 }6`78MyComponent.MyElement = styled.div``910export default MyBlock
Boom! 💥
Be careful however to use the babel-plugin-emotion to do that.
Bye bye and see you soon!