116

I'm beginning to use Facebook React in a Backbone project and so far it's going really well.
However, I noticed some duplication creeping into my React code.

For example, I have several form-like widgets with states like INITIAL, SENDING and SENT. When a button is pressed, the form needs to be validated, a request is made, and then state is updated. State is kept inside React this.state of course, along with field values.

If these were Backbone views, I would have extracted a base class called FormView but my impression was that React neither endorses nor supports subclassing to share view logic (correct me if I'm wrong).

I've seen two approaches to code reuse in React:

Am I correct that mixins and containers are preferred to inheritance in React? Is this a deliberate design decision? Would it make more sense to use a mixin or a container component for my “form widget” example from second paragraph?

Here's a gist with FeedbackWidget and JoinWidget in their current state. They have a similar structure, similar beginSend method and will both need to have some validation support (not there yet).

activatedgeek
  • 5,831
  • 3
  • 24
  • 47
Dan Abramov
  • 241,321
  • 75
  • 389
  • 492
  • As an update to this - react are having second thoughts about supporting mixins in the long future, because when your e.g. componentDidMount's are all just magically working react is doing some complicated stuff so they are not over-writing each other.. because mixins are very simplistic and not fit for purpose – Dominic May 28 '15 at 15:44
  • I don't have that much experience with React, but you could define your own mixin with functions that did not overlap with the namespace of actual React objects. then just call the "superclass"/composition object functions from your typical React components functions. then there is no overlap between React functions and inherited functions. this helps to reduce some boilerplate, but limits the magic happening and makes it easier for React itself to operate behind the scenes. is this really that hard to conceive? I hope I made myself clear. – Alexander Mills Jul 30 '15 at 21:17
  • Mixins will never die because you can always just make DIY mixins. React just won't have "native" support for mixins but you can still do mixins yourself with native JS. – Alexander Mills Jul 30 '15 at 21:18

2 Answers2

109

Update: this answer is outdated. Stay away from the mixins if you can. I warned you!
Mixins Are Dead. Long Live Composition

At first, I tried to use subcomponents for this and extract FormWidget and InputWidget. However, I abandoned this approach halfway because I wanted a better control over generated inputs and their state.

Two articles that helped me most:

It turned out to that I only needed to write two (different) mixins: ValidationMixin and FormMixin.
Here's how I separated them.

ValidationMixin

Validation mixin adds convenience methods to run your validator functions on some of your state's properties and store “error'd” properties in a state.errors array so you can highlight corresponding fields.

Source (gist)

define(function () {

  'use strict';

  var _ = require('underscore');

  var ValidationMixin = {
    getInitialState: function () {
      return {
        errors: []
      };
    },

    componentWillMount: function () {
      this.assertValidatorsDefined();
    },

    assertValidatorsDefined: function () {
      if (!this.validators) {
        throw new Error('ValidatorMixin requires this.validators to be defined on the component.');
      }

      _.each(_.keys(this.validators), function (key) {
        var validator = this.validators[key];

        if (!_.has(this.state, key)) {
          throw new Error('Key "' + key + '" is defined in this.validators but not present in initial state.');
        }

        if (!_.isFunction(validator)) {
          throw new Error('Validator for key "' + key + '" is not a function.');
        }
      }, this);
    },

    hasError: function (key) {
      return _.contains(this.state.errors, key);
    },

    resetError: function (key) {
      this.setState({
        'errors': _.without(this.state.errors, key)
      });
    },

    validate: function () {
      var errors = _.filter(_.keys(this.validators), function (key) {
        var validator = this.validators[key],
            value = this.state[key];

        return !validator(value);
      }, this);

      this.setState({
        'errors': errors
      });

      return _.isEmpty(errors);
    }
  };

  return ValidationMixin;

});

Usage

ValidationMixin has three methods: validate, hasError and resetError.
It expects class to define validators object, similar to propTypes:

var JoinWidget = React.createClass({
  mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],

  validators: {
    email: Misc.isValidEmail,
    name: function (name) {
      return name.length > 0;
    }
  },

  // ...

});

When user presses the submission button, I call validate. A call to validate will run each validator and populate this.state.errors with an array that contains keys of the properties that failed validation.

In my render method, I use hasError to generate correct CSS class for fields. When user puts focus inside the field, I call resetError to remove error highlight till next validate call.

renderInput: function (key, options) {
  var classSet = {
    'Form-control': true,
    'Form-control--error': this.hasError(key)
  };

  return (
    <input key={key}
           type={options.type}
           placeholder={options.placeholder}
           className={React.addons.classSet(classSet)}
           valueLink={this.linkState(key)}
           onFocus={_.partial(this.resetError, key)} />
  );
}

FormMixin

Form mixin handles form state (editable, submitting, submitted). You can use it to disable inputs and buttons while request is being sent, and to update your view correspondingly when it is sent.

Source (gist)

define(function () {

  'use strict';

  var _ = require('underscore');

  var EDITABLE_STATE = 'editable',
      SUBMITTING_STATE = 'submitting',
      SUBMITTED_STATE = 'submitted';

  var FormMixin = {
    getInitialState: function () {
      return {
        formState: EDITABLE_STATE
      };
    },

    componentDidMount: function () {
      if (!_.isFunction(this.sendRequest)) {
        throw new Error('To use FormMixin, you must implement sendRequest.');
      }
    },

    getFormState: function () {
      return this.state.formState;
    },

    setFormState: function (formState) {
      this.setState({
        formState: formState
      });
    },

    getFormError: function () {
      return this.state.formError;
    },

    setFormError: function (formError) {
      this.setState({
        formError: formError
      });
    },

    isFormEditable: function () {
      return this.getFormState() === EDITABLE_STATE;
    },

    isFormSubmitting: function () {
      return this.getFormState() === SUBMITTING_STATE;
    },

    isFormSubmitted: function () {
      return this.getFormState() === SUBMITTED_STATE;
    },

    submitForm: function () {
      if (!this.isFormEditable()) {
        throw new Error('Form can only be submitted when in editable state.');
      }

      this.setFormState(SUBMITTING_STATE);
      this.setFormError(undefined);

      this.sendRequest()
        .bind(this)
        .then(function () {
          this.setFormState(SUBMITTED_STATE);
        })
        .catch(function (err) {
          this.setFormState(EDITABLE_STATE);
          this.setFormError(err);
        })
        .done();
    }
  };

  return FormMixin;

});

Usage

It expects component to provide one method: sendRequest, which should return a Bluebird promise. (It's trivial to modify it to work with Q or other promise library.)

It provides convenience methods such as isFormEditable, isFormSubmitting and isFormSubmitted. It also provides a method to kick off the request: submitForm. You can call it from form buttons' onClick handler.

Dan Abramov
  • 241,321
  • 75
  • 389
  • 492
  • 2
    @jmcejuela In fact I moved to more component-ish approach later (still using mixins heavily), I might expand on this at some point.. – Dan Abramov May 27 '14 at 13:23
  • 1
    Is there some news about "more component-ish approach"? – NilColor Jul 12 '14 at 14:56
  • 3
    @NilColor Not yet, I'm not quite satisfied with it. :-) Currently I have `FormInput` that talks to its owner via `formLink`. `formLink` is like [`valueLink`](http://facebook.github.io/react/docs/two-way-binding-helpers.html), and is returned from `FormMixin`'s `linkValidatedState(name, validator)` method. `FormInput` gets its value from `formLink.value` and calls `formLink.requestBlur` and `formLink.requestFocus`—they cause validation in `FormMixin`. Finally, to customize the actual component being used for input, I can pass it to `FormInput`: `` – Dan Abramov Jul 12 '14 at 15:16
  • Nice answer - some tips: you don't have to call `done` in bluebird and the code will work as is in Q (or native promises) - of course bluebird is better. Also note that the syntax changed in React since the answer. – Benjamin Gruenbaum Mar 04 '15 at 19:35
4

I'm building an SPA with React (in production since 1 year), and I almost never use mixins.

The only usecase I currently have for mixins is when you want to share behavior that uses React's lifecycle methods (componentDidMount etc). This problem is solved by the Higher-Order Components that Dan Abramov talk in his link (or by using ES6 class inheritance).

Mixins are also often used in frameworks, to make framework API available to all the components, by using the "hidden" context feature of React. This won't be needed anymore either with ES6 class inheritance.


Most of the other times, mixins are used, but are not really needed and could be easiler replaced with simple helpers.

For example:

var WithLink = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={this.linkState('message')} />;
  }
});

You can very easily refactor LinkedStateMixin code so that the syntax would be:

var WithLink = React.createClass({
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={LinkState(this,'message')} />;
  }
});

Is there any big difference?

Sebastien Lorber
  • 79,294
  • 59
  • 260
  • 386
  • You are correct. In fact the LinkedStateMixin docs actually spell out how to do it without the mixin. This particular mixin is really just a little syntactic sugar. – nextgentech Sep 20 '15 at 22:08