29

I know how to get the theme from components that are created using the styled way:

const StyledView = styled.View`
    color: ${({ theme }) => theme.color};
`;

But how to get from normal components or apply it for different properties? Example:

index.js

<ThemeProvider theme={{ color: 'red' }}>
    <Main />
</ThemeProvider>

main.js

<View>
    <Card aCustomColorProperty={GET COLOR FROM THEME HERE} />
</View>

Notice how the property that needs the theme is not called style

mxstbr
  • 11,087
  • 4
  • 39
  • 37
Bruno Lemos
  • 8,847
  • 5
  • 40
  • 51

4 Answers4

37

You can use the useTheme hook since v5.0:

import React, { useTheme } from 'styled-components';

export function MyComponent() {
  const theme = useTheme();

  return <p style={{ color: theme.color }}>Text</p>;
}

You can also use the withTheme higher order component that I contributed a long time ago since v1.2:

import { withTheme } from 'styled-components'

class MyComponent extends React.Component {
  render() {
    const { theme } = this.props

    console.log('Current theme: ', theme);
    // ...
  }
}

export default withTheme(MyComponent)



original response below (ignore this!)

While there is no official solution, I came up by now:

Create a Higher Order Component that will be responsable to get the current theme and pass as a prop to a component:

import React from 'react';
import { CHANNEL } from 'styled-components/lib/models/ThemeProvider';

export default Component => class extends React.Component {
  static contextTypes = {
    [CHANNEL]: React.PropTypes.func,
  };

  state = {
    theme: undefined,
  };

  componentWillMount() {
    const subscribe = this.context[CHANNEL];
    this.unsubscribe = subscribe(theme => {
      this.setState({ theme })
    });
  }

  componentWillUnmount() {
    if (typeof this.unsubscribe === 'function') this.unsubscribe();
  }

  render() {
    const { theme } = this.state;

    return <Component theme={theme} {...this.props} />
  }
}

Then, call it on the component you need to access the theme:

import Themable from './Themable.js'
  
const Component = ({ theme }) => <Card color={theme.color} />

export default Themable(Component);
Bruno Lemos
  • 8,847
  • 5
  • 40
  • 51
  • I am attempting to use `withTheme` on a functional component like so: – aminimalanimal Jan 20 '17 at 07:20
  • `export const circle = withTheme(({theme}) => ())` – aminimalanimal Jan 20 '17 at 07:31
  • I'm getting an error that says that it's not returning a React Node, though... so I assume `withProps` doesn't currently work with functional components? – aminimalanimal Jan 20 '17 at 07:32
  • erg. typo. “so I assume `withProps`” was supposed to be “so I assume `withTheme`”. Sorry for so many comments—I accidentally hit enter on the first one, and wasn't able to delete it, and then wasn't allowed to edit it due to the 5 minute timer... and so on. – aminimalanimal Jan 20 '17 at 07:42
  • @aminimalanimal it should work fine. Is your component working without the withTheme part? If so, open an issue here: https://github.com/styled-components/styled-components/issues – Bruno Lemos Jan 20 '17 at 11:00
  • @aminimalanimal Wait, you are creating a variable called circle and calling a circle component inside of it. The names should be different, that's probably the problem. – Bruno Lemos Jan 20 '17 at 11:03
  • `circle` is a native svg element, so I doubt it'd matter if I did name my constant the same thing. That was just a simplified example though, not something I directly tried (was trying to demonstrate something that'd fit on one line). But yes, my components were working fine until I wrapped them with `withTheme`. I'll open an issue with a better description later tonight. – aminimalanimal Jan 20 '17 at 21:44
  • Thanks for your insight. It turns out that `withTheme` does indeed work on functional components; my issue was that React components absolutely **must** start with an uppercase letter, which I thought was simply a best-practice. (In the more complex example that I referenced, I wasn't actually creating a component without `withTheme`, so I was able to place it within my larger component with `{variableName}`; with `withTheme`, that syntax was no longer valid). – aminimalanimal Jan 22 '17 at 20:06
17

You can use useTheme hook

import { useTheme } from 'styled-components';

const ExampleComponent = () => {
  const theme = useTheme();

  return (
    <View>
       <Card aCustomColorProperty={theme.color.sampleColor} />
    </View>
  );
};
Yurii Brusentsov
  • 539
  • 4
  • 12
0

Creating a HOC is a good way to tackle theming. Let me share another idea using React's Context.

Context allows you to pass data from a parent node to all it’s children. Each child may choose to get access to context by defining contextTypes in the component definition.

Let's say App.js is your root.

import themingConfig from 'config/themes';
import i18nConfig from 'config/themes';
import ChildComponent from './ChildComponent';
import AnotherChild from './AnotherChild';

class App extends React.Component {
    getChildContext() {
        return {
            theme: themingConfig,
            i18n: i18nConfig, // I am just showing another common use case of context
        }
    }

    render() {
          return (
              <View>
                  <ChildComponent />
                  <AnotherChild myText="hola world" />
              </View>
          );
    }
}

App.childContextTypes = {
    theme: React.PropTypes.object,
    i18n: React.PropTypes.object
};

export default App;

Now our `ChildComponent.js who wants some theme and i18n strings

class ChildComponent extends React.Component {
    render() {
        const { i18n, theme } = this.context;

        return (
           <View style={theme.textBox}>
               <Text style={theme.baseText}>
                   {i18n.someText}
               </Text>
           </View>
        );
    }
}

ChildComponent.contextTypes = {
    theme: React.PropTypes.object,
    i18n: React.PropTypes.object
};

export default ChildComponent;

AnotherChild.js who only wants theme but not i18n. He might be stateless as well:

const AnotherChild = (props, context) {
    const { theme } = this.context;
    return (<Text style={theme.baseText}>{props.myText}</Text>);
}

AnotherChild.propTypes = {
    myText: React.PropTypes.string
};

AnotherChild.contextTypes = {
    theme: React.PropTypes.object
};

export default AnotherChild;
yadhu
  • 15,423
  • 7
  • 32
  • 49
  • Context is great, but I had problems with it. Components all the way down the tree wasn't receiving the new context value because context does not garantee propagation: if one component doesn't update, all it's children won't update either. – Bruno Lemos Nov 28 '16 at 23:18
  • 1
    I just found this article which explains the problem with Context and provides a solution: https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076 It turns out that this solution is what styled-components does internally and in my answer I'm subscribing to styled-components context. Seems good. What do you think? – Bruno Lemos Nov 28 '16 at 23:21
  • 1
    @BrunoLemos from my perspective your solution looks pretty good to me! We might add something like that to core or expose CHANNEL directly and add docs for this, would you mind submitting an issue with your code? Thank you! – mxstbr Nov 29 '16 at 18:36
0

To use withTheme in a functional component create a Higher-order-component.

Higher-order-component: higher-order components, or HOCs, are functions that take a component and output a new component after enhancing it in some manner:
const EnhancedHOCComponent = hoc(OriginalReactComponent)

Sample withTheme in a functional Component


const MyButton = ({theme}) => {
const red = theme.colors.red;

return (<div  style={{ color: red}} >how are you</div>)
}`

const Button =  withTheme(MyButton);
export default Button;
vsync
  • 118,978
  • 58
  • 307
  • 400