styled-components: withTheme should not break if a default theme is provided

We are migrating a quite big React UI-kit to styled components 🎉 🎉 🎉 but found the following problem, every time we want to do some theming, the code for our components looks like this:

import * as theme from '../Theme'; // this is our fallback theme

const Title = styled.span`
    font-weight: bold;
    color: ${props => props.theme.color || theme.color};
    font-size: {props => props.theme.fontSizeMedium || theme.fontSizeMedium}
`;

And the code gets even uglier when you have to adjust the styling to conditional prop values… you can imagine.

Besides that, when using withTheme HOC in components and you don’t wrap everything in a ThemeProvider the component itself breaks.

Is there a better/nicer way to provide a default ThemeProvider without having to do all that boilerplate code?

Thanks in advance and awesome work btw! ❤️

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 18 (18 by maintainers)

Most upvoted comments

I would recommend you export your own ThemeProvider that sets a default theme in case the users don’t provide a custom one. Don’t even need .defaultProps everywhere in that case, but of course if you prefer users are aware of you using styled-components using defaultProps will work perfectly too!

See this draft documentation we never finished: https://github.com/styled-components/styled-components-experimentation/blob/master/component-libraries/shared-component-libraries.md

You can use the defaultProps to define fallbacks.

const Title = styled.span`
    font-weight: bold;
    color: ${props => props.theme.color};
    font-size: {props => props.theme.fontSizeMedium}
`;

Title.defaultProps = {
  theme: defaultTheme
}

Besides that, when using withTheme HOC in components and you don’t wrap everything in a ThemeProvider the component itself breaks.

It shouldn’t actually break but just output a console.error:

https://github.com/styled-components/styled-components/blob/master/src/hoc/withTheme.js#L37

No worries, just merge the passed-in theme with the default theme:

// component-lib/ThemeProvider.js
import { ThemeProvider } from 'styled-components';

const ComponentLibThemeProvider = (props) => {
  const theme = {
-    myComponentLib: props.theme,
+    myComponentLib: Object.assign({}, defaultTheme, props.theme),
    // Namespace the theme for the user
  };
  return (
    <ThemeProvider theme={theme}>
      {props.children}
    </ThemeProvider>
  );
}

export default ComponentLibThemeProvider;

That way your users can override specific parts of the theme, but for all the rest (or if none is provided, the whole thing) the default theme stays in place!

Ok, I want to give this a try but feel a bit lost about the codebase, could someone give me a short tour? 😄

@kutyel @mxstbr

I think if you provide a default theme to your withThemed component it shouldn’t throw anymore?

To be referring to my last comment, there’s no error being thrown inside withTheme:

https://github.com/styled-components/styled-components/blob/master/src/hoc/withTheme.js#L35

But I agree that it’s confusing that we are logging an error even if the prop is defined. This however also reminded me that the behaviour of withTheme is very counterintuitive in general as it doesn’t match the StyledComponentss usage of the theme prop/defaultProp.

https://github.com/styled-components/styled-components/blob/master/src/models/StyledComponent.js#L100

These lines here should be adapted to be a generic, reusable function, since it’s already used twice inside the component. We can reuse this error and log a warning if there’s both no context, and no theme prop.

The resulting component from the withTheme HOC should match the logic exactly so that:

  • On mount we need to determine whether there’s a theme context and subscribe to it
  • We need to make sure that the theme prop takes precedence over the provided theme (from context) and use it instead, if it was passed
  • We need to make sure that the default theme prop doesn’t take precedence over the provided theme (from context)
  • If no theme context is passed we need to use the theme prop at all times
  • If all three options are not present we need to log an error/warning (to be defined cc @mxstbr)

So overall this should be a sufficient overview of how to match the withTheme implementation to the StyledComponent implementation.

cc @mxstbr: Just making sure, I hope this makes more sense than just getting rid of the console.error?