theme icon indicating copy to clipboard operation
theme copied to clipboard

Hot reloading is not working

Open dnish opened this issue 7 years ago • 5 comments

Hey, I'm having an issue with my custom theme and hot reloading. If I change some values of my theme, f.e. the backgroundColor, I see a "Hot reloading..." message on my phones screen, but the changes are not reflected until I do a full reload of my app. Is there any workaround for this issue? It would be very nice for our workflow if we could see changes without doing a full reload of our app.

dnish avatar Mar 13 '17 15:03 dnish

Same thing is happening to me. Is there any workaround which can keep the state so that I can keep the same page visible with the same data while changing the design of it?

reyalpsirc avatar May 11 '17 12:05 reyalpsirc

Okay, I've been able to tweak some code in order to have the Hot Reloading working. Please note that I'm not sure if this is the best way but, at least it works for me.

For this to work, you'll need to have your styles in separate files and require each one of them on the theme.js file inside the buildTheme function. You can see an example of one of those files at the end of this comment.

Also, each Component will need to use connectStyleHotReload of the theme.js file instead of the default connectStyle.

theme.js:

import { connectStyle, Theme } from '@shoutem/theme'
import hoistStatics from 'hoist-non-react-statics'
import { getTheme } from '@shoutem/ui'
import React, {Component} from 'react'

function buildTheme () {
  return Object.assign({}, getTheme(), {
    // Set your theme styles here using require
    'containers.SplashscreenPage': { ...(require('./styles/containers/#SplashscreenPage').default)(commonStyles) },
    'components.DefaultButton': { ...(require('./styles/components/#DefaultButton').default)(commonStyles) }
  })
}

var commonStyles = {
  // If you have some common styles to use accross multiple components, define them here and call them on the files
  mainAppActiveColor: {
    backgroundColor: '#4286f4'
  },
  navigationBar: {
    alignSelf: 'stretch',
    backgroundColor: '#CCCCCC'
  },
  navigationBarTile: {
    color: 'black',
    fontSize: 18,
    marginTop: 7
  }
}
if (!window.themeData) {
  window.themeData = buildTheme()
}
var theme = window.themeData

function checkIfThemeKeyChanged (keyName, newValue) {
  return !(JSON.stringify(newValue) === JSON.stringify(window.themeData[keyName]))
}

if (module.hot) {
  if (!window.themeCallbacks) {
    window.themeCallbacks = {}
  }
  module.hot.accept(() => {
    var newTheme = buildTheme()
    Theme.setDefaultThemeStyle(newTheme)
    var keys = Object.keys(newTheme)
    for (var i = 0; i < keys.length; i++) {
      var keyName = keys[i]
      var hasChanged = checkIfThemeKeyChanged(keyName, newTheme[keyName])
      if (hasChanged) {
        if (window.themeCallbacks[keyName]) {
          for (var k = 0; k < window.themeCallbacks[keyName].length; k++) {
            window.themeCallbacks[keyName][k]()
          }
        }
      }
    }
    window.themeData = newTheme
    theme = window.themeData
  })
}

function registerCallback (componentStyleName, callback) {
  if (module.hot) {
    if (!window.themeCallbacks[componentStyleName]) {
      window.themeCallbacks[componentStyleName] = []
    }
    var position = window.themeCallbacks[componentStyleName].length
    window.themeCallbacks[componentStyleName].push(callback)
    return position
  }
  return null
}

function unregisterCallback (componentStyleName, callbackPosition) {
  if (module.hot) {
    if (window.themeCallbacks[componentStyleName] && callbackPosition < window.themeCallbacks[componentStyleName].length) {
      window.themeCallbacks[componentStyleName].splice(callbackPosition, 1)
    }
  }
}

function connectStyleHotReload (componentStyleName, componentStyle = {}, mapPropsToStyleNames, options = {}) {
  function getComponentDisplayName (WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component'
  }

  return (WrappedComponent) => {
    const componentDisplayName = getComponentDisplayName(WrappedComponent)
    var BoundComponent = connectStyle(componentStyleName, componentStyle, mapPropsToStyleNames, options)(WrappedComponent)

    class CustomStyledComponent extends Component {
      static displayName = `Styled(${componentDisplayName})`;
      static WrappedComponent = WrappedComponent;
      componentWillMount () {
        this.callbackPosition = registerCallback(componentStyleName, () => {
          if (this.refs.boundInst) {
            this.refs.boundInst.context.theme = Theme.getDefaultTheme()
            var nextContext = this.refs.boundInst.context
            var nextProps = this.refs.boundInst.props
            var styleNames = this.refs.boundInst.resolveStyleNames(nextProps)
            const resolvedStyle = this.refs.boundInst.resolveStyle(nextContext, nextProps, styleNames)
            this.refs.boundInst.setState({
              ...this.refs.boundInst.state,
              style: resolvedStyle.componentStyle,
              childrenStyle: resolvedStyle.childrenStyle
            })
          } else {
            console.log('Hot reload failed. Please use "connectStyleHotReload" on Component "' + componentDisplayName + '"')
          }
        })
      }
      componentWillUnmount () {
        unregisterCallback(componentStyleName, this.callbackPosition)
      }
      render () {
        return (
          <BoundComponent
            ref='boundInst'
            {...this.props}
          />)
      }

    }
    return hoistStatics(CustomStyledComponent, WrappedComponent)
  }
}

export {
  theme,
  connectStyleHotReload,
  commonStyles
}

Example of style file (#DefaultButton.js):

export default (commonStyles) => {
  return {
    button: {
      borderRadius: 10
    },
    enabled: {
      ...commonStyles.mainAppActiveColor
    },
    disabled: {
      ...commonStyles.mainAppActiveColor
    },
    text: {
      fontSize: 12
    }
  }
}

reyalpsirc avatar May 11 '17 21:05 reyalpsirc

This is really a big missing feature, cuts a lot of development time having to manually reload the screen every time.

filipepratalima avatar Sep 14 '17 16:09 filipepratalima

This is a big issue for me since I need to dynamically reload styles in my app in certain cases so it isn't just an issue during development.

There is a patch here which works https://github.com/GeekyAnts/theme/pull/1

patrickgalbraith avatar Sep 19 '17 00:09 patrickgalbraith

Hello, anyone found the way to resolve this issue?

huyphamwork avatar Aug 24 '18 09:08 huyphamwork