javascriptrubyreactjshyperstack

Higher-order components in Hyperstack


There's a frequent usecase with javascript libraries where you want to decorate your components with higher order components.

For example, the material-ui library includes a styling higher-order component withStyles.

In javascript, you would do

import { withStyles } from '@material-ui/core';

const styles = {
  example: {
    padding: 8
  }
}
const SomeComponent = ({classes}) => <div className={classes.example}>I'm a component</div>;

export default withStyles(SomeComponent);

How can you achieve the same in Hyperstack?


Solution

  • First off looks like there is an issue that you have to patch. This will be fixed in the next point release: Just add this method to your Hypercomponent base class (app/hyperstack/components/hypercomponent.rb)

    def self.to_n
      Hyperstack::Internal::Component::ReactWrapper.create_native_react_class(self)
    end
    

    Now if you have the following styles:

    MY_STYLES = {root: {backgroundColor: 'red'}}
    

    and a component that you want to style:

    class StyleTest < HyperComponent
      param :some_param
      param :classes
      render do
        DIV(class: classes[:root]) { some_param }
      end
    end
    

    You can do so like this:

    class StyledTest1 < HyperComponent
      imports `Mui.withStyles(#{MY_STYLES.to_n})(#{StyleTest.to_n})`
    end
    

    What is happening is we are dropping out to JS using the backticks and calling Mui.with_styles directly and passing it MY_STYLES (just like in the MUI doc example). The to_n converts from a Ruby Hash to JS object. (When passing params to components this is automatic, but not so with simple function calls.)

    Then we call the resulting HOC with our StyleTest class (also calling to_n to convert from a Ruby class to a simple JS class.)

    Finally, we import it back into a Hyperstack component class.

    That is a little more work than I like so we can just add a handy helper method to our HyperComponent class:

    class HyperComponent
      include Hyperstack::Component
      include Hyperstack::State::Observable
      param_accessor_style :accessors  # this is now the prefered param style
    
      # patch for issue: https://github.com/hyperstack-org/hyperstack/issues/153
      def self.to_n
        Hyperstack::Internal::Component::ReactWrapper.create_native_react_class(self)
      end
    
      # our helper macro:
      def self.add_styles(style, opts = {})
        imports `Mui.withStyles(#{style.to_n}, #{opts.to_n})(#{superclass.to_n})`
      end
    end
    

    Now we can add styles like this:

    class StyledTest2 < StyleTest
      add_styles MY_STYLE
    end
    

    and now we have a new Component Class with our style in it.

    For example:

    class App < HyperComponent
    
      render do
        DIV do
          StyledTest1(some_param: 'test 1')
          StyledTest2(some_param: 'test 2')
        end
      end
    end