This article is a Hall of Shamer™ because I think writting object oriented React is the wrong approach for a number of reasons.
React has removed automatically exporting factory functions from createClass. This means, components can no longer be called as functions that accept their properties. In order to get the same effect, you must call React.createFactory before calling your component as a function.
This:
var MyComponent = React.createClass({
render() {
return <h1>Hello {this.props.name}</h1>;
},
});
MyComponent({
name: "Merrick",
});
Becomes this:
const MyComponent = React.createFactory(
class extends React.Component {
render() {
return <h1>Hello {this.props.name}</h1>;
}
}
);
MyComponent({
name: "Merrick",
});
To be clear, the recommended use in your application's code is to leverage JSX. In order to follow along with this article, without the overhead of JSX, components should be wrapped in createFactory. (Which JSX effectively does for you by compiling to React.createElement). See this deprecation notice for more information.
I love React.js. I find it to be a powerful tool for creating UI and revel in its immediate mode rendering model. Unforunately however, the dominant approach for writing React applications is use singletons at the module level. Here is an example of what I mean:
var count = 0;
module.exports = {
increment() {
return count++;
},
getCount() {
return count;
},
};
The trouble with this approach is that it is difficult to test. You typically need to add some sort of reset functionality to your module, like this:
var count = 0;
module.exports = {
increment() {
return count++;
},
getCount() {
return count;
},
reset() {
count = 0;
},
};
That way in your unit tests you can reset the corresponding state to its original place. This can get very complex and tedious depending on how much or how complex the state in your module is. Because of this people tend to just throw away the entire module and re-evaluate it each time. That way, each test gets the benefit of fresh state, and you don't have to write reset methods. This is the way Facebook's Jest works and my own library, Squire.js. This is problematic for a few reasons.
require
is a
service locator, not
a dependency injector.
Using it for both conflates it's use violating the
single responsibility principle.require()
.I wanted to offer a different approach that seems to solve the requirements I have which are:
The method for passing anything to a React Element is to use properties, properties are passed in as the first argument of a React Element. For example:
var MyComponent = React.createClass({
render() {
return <h1>Hello {this.props.name}</h1>;
},
});
MyComponent({
name: "Merrick",
});
This would render the following HTML:
<h1>Hello Merrick</h1>
Properties are effectively the technique one can use to pass things to the component which are not child elements. The neat thing is you can even set default properties. Check this out:
var MyComponent = React.createClass({
getDefaultProps() {
return {
name: "Scuba Steve",
};
},
render() {
return <h1>Hello {this.props.name}</h1>;
},
});
MyComponent();
This would render what you would expect:
<h1>Hello Scuba Steve</h1>
Have you made the connection yet? We can use React Properties to inject dependencies to our components. Check this out:
var MyComponentViewModel = require("./MyComponentViewModel");
var HTTP = require("http");
var MyComponent = React.createClass({
getDefaultProps() {
return {
model: new MyComponentViewModel(new HTTP()),
};
},
getInitialState() {
return this.props.model.getState();
},
render() {
return <h1>Hello {this.state.name}</h1>;
},
});
MyComponent();
Now, the code is just as tractable as it is using the singleton approach, you can see right where the dependencies exist on the file system, but here is where it gets cool... We can pass in a different view model under test, like this:
var MyComponentViewModel = require("./MyComponentViewModel");
var mockHTTP = {
get: function () {
// Would probably return a promise.
return {
name: "Async Name",
};
},
};
MyComponent({
model: new MyComponentViewModel(mockHTTP),
});
With this approach we get the following benefits:
A neat side-effect of this approach is that we can use propTypes to validate our dependencies honor a specific interface. This encourages us to code to an interface, not and implementation.
var MyComponent = React.createClass({
propTypes: {
model: React.PropTypes.shape({
getState: React.PropTypes.func,
}),
},
getDefaultProps() {
return {
model: new MyComponentViewModel(new HTTP()),
};
},
getInitialState() {
return this.props.model.getState();
},
render() {
return <h1>Hello {this.state.name}</h1>;
},
});
This will validate that our model has a getState
method! How cool is that!?
Now we are really coding to an interface and we get that validated by React's
type system.
This is a new idea and I'm not positive how great it is. I would love to hear critisism and feedback in all its forms, preferably twitter or email.
Learn about the structurally edited language that powers sites built with Webflow.
2 min read »
Configure Netlify to send particular routes to Webflow so that you can selectively serve pages that are designed and hosted on Webflow.
3 min read »
Learn about how programming languages work as we design & implement a little Lisp-like language called JSON Lisp.
14 min read »
JavaScript's call by sharing and getter semantics allow us to implement lazy evaluation for field access.
8 min read »