javivelasco / react-css-themr

Easy theming and composition for CSS Modules.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ThemeProvider update theme?

nilssonan opened this issue · comments

I have 2 themes.

Want to be able to switch between them inside the application via a profile menu.

Wrapped ThemeProvider and pass the new theme to it but children of ThemeProvider does not receive the new theme. Childrens render-functions triggers but the props are the same. Problem with context?

@nilssonan This happens because context changes are ignored by intermediate components with shouldComponentUpdate checks. facebook/react#2517
Also related: #25

I'm doing something similar with React Storybook and ran into this problem as well. I looked at the implementation of the higher-order component and it does not update the value of this.theme_ unless specific props are modified when re-rendered.

componentWillReceiveProps(nextProps) {
  if (
    nextProps.composeTheme !== this.props.composeTheme ||
    nextProps.theme !== this.props.theme ||
    nextProps.themeNamespace !== this.props.themeNamespace
  ) {
    this.theme_ = this.calcTheme(nextProps)
  }
}

As discussed in #25, a solution is needed for updating children further down the tree when the context is changed. Anyways, I was able to get around this by passing a different key to the ThemeProvider when rendered with a different theme. I'm okay with the hack since I'm only doing this for non-production code.

@ctumolosus, I use now your hack :) But maybe guys of this library are going to fix it somehow? Is it possible at all?

@pavel06081991

Is it possible at all?

Yes it's possible. Theme object in context should be replaced with any kind of observable (likely the fastest available for internal usage only) instead of a plain value. Then all comsumers (decorated with themr) should subscribe to that observable taken from context and merge with theme coming from props.
But there will likely be some performance impact which should be considered. Maybe such behavior with observable value in context can be placed behind a prop to ThemrProvider.

Edit: I think this should be discussed because the problem with context and shouldComponentUpdate is more generic and relates to React, not to themr. +@javivelasco

Yep this is something more related to React than the library. Plus as @raveclassic says there are solutions to make it work so I'm rather closing :)

Thank for your patience!

@ctumolosus
Could you provide an example of your solution?

@czebe There's no solution currently as well as any example :) It was just a suggestion for further implementation.

Sorry, I accidentally missed that you were not addressing my comment.

ThemeChooser is the parent of all components returned by my React Storybook stories. It renders a toolbar with a dropdown that you can use to select the desired theme and wraps its children in ThemeProvider. As stated in my previous comment, the trick is to update the key prop of ThemeProvider to force render its children with the selected theme.

import React, { Component } from 'react';
import { ThemeProvider } from 'react-css-themr';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import LightTheme from './themes/LightTheme';
import DarkTheme from './themes/DarkTheme';
import styles from './ThemeChooser.css';

const themes = {
  light: LightTheme,
  dark: DarkTheme,
};

export default class ThemeChooser extends Component {

  static displayName = 'ThemeChooser';

  static propTypes = {
    children: PropTypes.node.isRequired,
  };

  state = {
    theme: 'light',
  };

  handleChange = (event) => {
    this.setState({ theme: event.target.value });
  }

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

    return (
      <div className={classnames(styles.root)}>
        <div className={classnames(styles.toolbar)}>
          <select value={this.state.theme} onChange={this.handleChange}>
            {Object.keys(themes).map((value) => (
              <option key={value} value={value}>{value}</option>
            ))}
          </select>
        </div>
        <div className={classnames(styles.content)}>
          <ThemeProvider key={theme} theme={themes[theme]}>
            {children}
          </ThemeProvider>
        </div>
      </div>
    );
  }
}

Thanks Christian, this works like charm:
<ThemeProvider key={theme} theme={themes[theme]}>