Scale the ingredients of a recipe up/down I wrote a React app to adjust the serving size of a recipe. For example, if the recipe serves 4 people and requires 4 eggs, scaling it down to serve only 1 person should only require 1 egg. I try to also keep the units singular/plural accordingly while converting units when they become too big or too small. These features are written in plain JS, but I'd be curious to hear if it would be more appropriate to do this in React and how.

Relevant code snippets:



const RECIPES = [{
  id: 'pasta-dough',
  name: 'Pasta Dough',
  serves: 4,
  ingredients: [
    {quantity: '4', unit: 'eggs'},
    {quantity: '4', unit: 'cups', item: 'flour'},
    // Note that the quantity below would normally be '1/4'.
    {quantity: '.25', unit: 'cup', item: 'olive oil'},
    {quantity: '1', unit: 'tablespoon', item: 'salt'}
  ]
}];

const Recipe = React.createClass({
  getInitialState: function() {
    return {
      serves: this.props.serves,
      scale: 1
    };
  },
  onChange: function(e) {
    const serves = Math.floor(Math.abs(e.target.value));
    this.setState({
      serves: serves,
      scale: serves / this.props.serves
    });
  },
  render: function() {
    return (
      &lt;section&gt;
        &lt;h1&gt;{this.props.name}&lt;/h1&gt;
        &lt;h2&gt;
          Serves
          &lt;input
            type="number" min="0"
            value={this.state.serves}
            /* TODO: Smarter default step. */
            step={this.props.serves / 4}
            onChange={this.onChange} /&gt;
        &lt;/h2&gt;
        &lt;Ingredients ingredients={this.props.ingredients} scale={this.state.scale} /&gt;
      &lt;/section&gt;
    );
  }
});

const Ingredients = React.createClass({
  render: function() {
    return (
      &lt;ul&gt;
        {this.props.ingredients.map((ingredient, i) =&gt;
          &lt;Ingredient key={i} scale={this.props.scale} {...ingredient} /&gt;
        )}
      &lt;/ul&gt;
    );
  }
});

const Ingredient = React.createClass({
  render: function() {
    // Commenting out the code that depends on other code libraries.
    // Want to keep this code review as simple as possible.
    // const quantity = new Quantity(this.props.quantity).multiply(this.props.scale);
    const unit = this.props.unit;
    // const measurement = new Measurement(quantity, unit).convertUnits();
    const quantity = this.props.quantity * this.props.scale;
    const measurement = `${quantity} ${unit}`
    return(
      &lt;li&gt;{measurement.toString()} {this.props.item}&lt;/li&gt;
    );
  }
});

RECIPES.forEach((recipe, i) =&gt; {
  ReactDOM.render(
    &lt;Recipe key={recipe.id} index={i} {...recipe}/&gt;,
    document.getElementById(recipe.id));
});

document.querySelector('h2 input').focus();
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"&gt;&lt;/script&gt;
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"&gt;&lt;/script&gt;

&lt;div id="pasta-dough"&gt;&lt;/div&gt;




Demo: https://rviscomi.github.io/scale-recipe-react/
Source: https://github.com/rviscomi/scale-recipe-react

I'm new to React and would really appreciate advice on writing more semantic React. Some specific areas I'm interested in:


use of state vs props
mounting
component organization
static methods


Things I'm not interested to get feedback on:


general JS style
ES6, unless it directly affects React
markup semantics
I have edited your little code snippet and wrote it in ES6, cause sooner or later you will need it. Most tutorials are written in ES6.
Mostly you will use a build tool like webpack in building react apps. 

Example structure:


dist/


js/


bundle.js

css/


style.css

index.html

src/


libs/ (js libs)
components/
  (one folder for every component to have all files together e.g. css with the use of css modules)


Ingredient/


Ingredient.jsx
Ingredient.css 

Ingredients/


Ingredients.jsx
Ingredients.css


App.jsx
style.css

webpack.config.js


Some useful links:


https://github.com/srn/react-webpack-boilerplate
https://github.com/gaearon/react-hot-boilerplate
http://survivejs.com/
https://webpack.github.io/
https://github.com/css-modules/webpack-demo




const RECIPES = [{
    id: 'pasta-dough',
    name: 'Pasta Dough',
    serves: 4,
    ingredients: [
        {quantity: '4', unit: 'eggs'},
        {quantity: '4', unit: 'cups', item: 'flour'},
        // Note that the quantity below would normally be '1/4'.
        {quantity: '.25', unit: 'cup', item: 'olive oil'},
        {quantity: '1', unit: 'tablespoon', item: 'salt'}
    ]
}];


class Ingredient extends React.Component {
    render() {
        let quantity = this.props.quantity * this.props.scale;

        return (
            &lt;li&gt;
              {quantity} {this.props.unit} {this.props.item}
            &lt;/li&gt;
        );
    }
}


class Ingredients extends React.Component {
    render() {
        return (
            &lt;ul&gt;
                {this.props.ingredients.map((ingredient, i) =&gt;
                    &lt;Ingredient key={i} scale={this.props.scale} {...ingredient} /&gt;
                )}
            &lt;/ul&gt;
        );
    }
}


class Recipe extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            serves: this.props.serves,
            scale: 1
        }

        this.onChange = this.onChange.bind(this);
    }

    onChange(e) {
        const serves = Math.floor(Math.abs(e.target.value));

        this.setState({
            serves: serves,
            scale: serves / this.props.serves
        });
    }


    render() {
        return (
            &lt;section&gt;
                &lt;h1&gt;{this.props.name}&lt;/h1&gt;
                &lt;h2&gt;
                    Serves
                    &lt;input
                        type="number" min="0"
                        value={this.state.serves}
                        /* TODO: Smarter default step. */
                        step={this.props.serves % 4}
                        onChange={this.onChange}/&gt;
                &lt;/h2&gt;
                &lt;Ingredients ingredients={this.props.ingredients} scale={this.state.scale}/&gt;
            &lt;/section&gt;
        );
    }
}


class App extends React.Component {
    render() {
        return &lt;div&gt;
            {RECIPES.map((recipe, i) =&gt; {
                return &lt;Recipe {...recipe} key={i}/&gt;;
            })}
        &lt;/div&gt;;
    }
}


ReactDOM.render(
    &lt;App /&gt;,
    document.getElementById('recipes'));
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset="utf-8"&gt;
  &lt;meta name="viewport" content="width=device-width"&gt;
  &lt;title&gt;JS Bin&lt;/title&gt;
  &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"&gt;&lt;/script&gt;
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"&gt;&lt;/script&gt;

&lt;/head&gt;
&lt;body&gt;
&lt;div id="recipes"&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;