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:
- Mixins (like LinkedStateMixin that ships with React);
- Container Components (such as react-infinite-scroll).
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).
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:
You can very easily refactor
LinkedStateMixin
code so that the syntax would be:Is there any big difference?
At first, I tried to use subcomponents for this and extract
FormWidget
andInputWidget
. However, I abandoned this approach halfway because I wanted a better control over generatedinput
s and their state.Two articles that helped me most:
It turned out to that I only needed to write two (different) mixins:
ValidationMixin
andFormMixin
.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)
Usage
ValidationMixin
has three methods:validate
,hasError
andresetError
.It expects class to define
validators
object, similar topropTypes
:When user presses the submission button, I call
validate
. A call tovalidate
will run each validator and populatethis.state.errors
with an array that contains keys of the properties that failed validation.In my
render
method, I usehasError
to generate correct CSS class for fields. When user puts focus inside the field, I callresetError
to remove error highlight till nextvalidate
call.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)
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
andisFormSubmitted
. It also provides a method to kick off the request:submitForm
. You can call it from form buttons'onClick
handler.