How do I solve this using composition instead of mixins in React?

Let's say I have a dataset of car sales and I am building components that display charts that show different aspects of that data.

One of the components could, for example, show the average selling price of cars over the years, while the other could show how the number of cylinders is related to mileage in cars purchased in the last year.

These components can be additionally parameterized so that they only display data for vehicles purchased in a given country, etc. It looks like using props to pass both parameters and data seems appropriate, so these components can be used like this:

<MileageByCylinder country="US" year="2014" dataset={data} />
<AvgSellingPricesByYear country="US"  dataset={data} />

      

So far so good. Let's say, however, that I am building two views (pages) that make up these components in different ways. The first one will statically show sales prices by year for different countries, while the other will have a user interface for selecting a country as well as showing cylinder mileage. Both views must somehow obtain the dataset that they pass to the chart components. But how can you reuse logic to collect data?

With mixins, this can be done like this:

var CarDataMixin = {

  componentDidMount: {
    // fetch car data and
    // call this.setState({carData: fetchedData}),
    // once data has been (asynchronously) fetched
  }

}

var FirstView = React.createClass({

  mixins: [CarDataMixin],

  render: function() {
    return (
        <div>
          <AvgSellingPricesByYear country="US"  dataset={this.state.carData} />
          <AvgSellingPricesByYear country="UK"  dataset={this.state.carData} />
          <AvgSellingPricesByYear country="FI"  dataset={this.state.carData} />
        </div>
      )
   }

})

var SecondView = React.createClass({

  mixins: [CarDataMixin],

  handleNewCountry: function(country) {
    this.state{country: country}
  },

  render: function() {
    return (
        <div>
          <CountryChooser onChange={this.handleNewCountry} />
          <MileageByCylinder country="{country}" year="2014" dataset={this.state.carData} />
          <AvgSellingPricesByYear country="{country}"  dataset={this.state.carData} />
        </div>
      )
   }

})

      

Good good. But many people advise against using mixins and this composition should be used instead. The only way to solve this problem using composition that I came up with is this:

  • Create a root component that passes its state to the children as props:

    var CarSalesRoot = React.createClass({
        componentDidMount: {
          // fetch car data and
          // call this.setState({carData: fetchedData}),
          // once data has been (asynchronously) fetched
        }
    
        renderChildren: function () {
            return React.Children.map(this.props.children, function (child) {
              return React.addons.cloneWithProps(child, {
                  carData: this.state.carData
              })
            }.bind(this))
        },
    
    
        render: function() {
          return <div>{this.renderChildren()}</div>
        } 
    });
    
          

  • Create a view without mixin:

    var FirstView = React.createClass({
        render: function() {
          return (
              <div>
                <AvgSellingPricesByYear country="US"  dataset={this.props.carData} />
                <AvgSellingPricesByYear country="UK"  dataset={this.props.carData} />
                <AvgSellingPricesByYear country="FI"  dataset={this.props.carData} />
              </div>
            )
         }
    });
    
          

  • Create a wrapper component that wraps both the root component and the main view component:

    var FirstViewMain = React.createClass({
        render: function() {
          return (
              <CarSalesRoot>
                <FirstView />
              </CarSalesRoot>
            )
         }
    });
    
          

This is a little more complex and makes the data streams less explicit. I feel like this is something very basic and should be resolved in a clean way. Am I missing something? Or is this really an idiomatic and clean solution?

+3


source to share


1 answer


Another use case for composition is to create a "higher-order component", which is similar to a higher-order function in functional programming, but is really just a wrapper, which is a slight change to the code you presented.

  • Define a higher order component:

    var bindToCarData = function (Component) {
      return React.createClass({
        componentDidMount: {
          // fetch car data and
          // call this.setState({carData: fetchedData}),
          // once data has been (asynchronously) fetched
        },
    
        render: function() {
          return <Component carData={ this.state.carData } />
        }
      });
    });
    
          

  • Then wrap your component when you define it.

    var FirstView = bindToCarData(React.createClass({
      render: function() {
        return (
            <div>
              <AvgSellingPricesByYear country="US"  dataset={this.props.carData} />
              <AvgSellingPricesByYear country="UK"  dataset={this.props.carData} />
              <AvgSellingPricesByYear country="FI"  dataset={this.props.carData} />
            </div>
          )
       }
    }));
    
          



This saves you the hassle of writing an additional component (number 3 in your question) and links in the data flow logic directly to the component that needs the data.

You can pass additional parameters to the function bindToCarData

if you need.

+6


source







All Articles