Search code examples
reactjsthemingstyled-componentsemotion

Using Emotion CSS-in-JS with theming in React


First off, I'm fairly new to React, so I'm still learning my way around.

I'm following the Introduction Article (Medium.com) on setting up using Themes with Emotion. But I'm stuck with trying to use a theme color in a const that will be used within a compose

For instance, I have:

const types = {
  primary: (props) => css`color: ${props.theme.blue}`,
  secondary: (props) => css`color: ${props.theme.red}`
};

const Button = withTheme(styled.button`
  composes: ${props => types[props.type]};
`);

(This is a contrived example. In reality, my primary and secondary will have a lot more CSS.)

If I render <Button type="primary">A Button</Button>, the color doesn't get applied. In fact, if I inspect the element, I don't even see a color style.

However, if instead I change Button to:

const Button = withTheme(styled.button`
  composes: ${types.primary};
`);

Then I see the correct color being applied.

I'm not totally sure what I'm doing wrong here.


Solution

  • Just a little background:

    Tagged template literals of ES2015 are template literals that can be parsed by a function by 'tagging' it with one (such as styled.button). That function receives the template literal and all ${} placeholders and returns the resulting string. ${} can contain anything considered a javascript expression, e.g. a single value, function, etc.

    In the case of styled from emotion, if you pass a function into any of the placeholders, it will call that function, passing in the props of the styled element you have used (in your example a button) as the first argument. If you wrap the styled template literal with a withTheme call, that props argument object will contain the theme prop you originally provided to the <ThemeProvider> at your app's base component.

    In your example, the reason it works for the second code block is because you are passing a function that will return a value. In the first code block you as passing a function that when called will return another function. This means the resulting style will contain a function, rather than a value.

    const types = {
      primary: (props) => css`color: ${props.theme.blue}`,
      secondary: (props) => css`color: ${props.theme.red}`
    };
    
    const Button = withTheme(styled.button`
      composes: ${props => types[props.type]};
    `);
    

    In the case of 'primary' the above evaluates to:

    const Button = withTheme(styled.button`
      composes: ${props => (props) => css`color: ${props.theme.blue}`};
    `);
    

    As you can see that is one level too deep. The theme will get passed in as part of props but the second deeper function would need to be called for the css function to then be called. In the second code block, 'primary' would evaluate to:

    const Button = withTheme(styled.button`
      composes: ${(props) => css`color: ${props.theme.blue}`};
    `);
    

    This would give the correct result as styled.button will pass the props in and css uses them within the called function directly.

    Hopefully this makes some sense. This is my first stack overflow answer attempt so I am happy to improve it if it can be.