React 0.14 introduced the ability to use pure functions as components. The react team calls them functional components in their announcement. The rest of the world calls them pure components.
This repo demonstrates pure components. It's based on the React Transform Boilerplate and features:
- Examples of pure components.
- Pure component factories, so you can use a single React instance, even if you load React from CDN.
- Unit test example with tape, demonstrating an easy way to test pure components.
Clone the repo & install:
git clone git@github.com:ericelliott/react-pure-component-starter.git
cd react-pure-component-starter
npm install
Optionally set environment variables HOST and PORT, start the dev server and follow instructions: As of this writing:
Cloud9 (c9.io) HOST='0.0.0.0' PORT=8080
Nitrous.io HOST='0.0.0.0' PORT=3000
The environment variables can be set locally in bash terminal using export (e.g., export PORT=8080).)
npm start
In another terminal window, start the dev console for lint/test feedback when you save files:
npm run watch
Pure component factories let you inject your React instance into the component so that you can share a single React instance across your entire app -- even if you load React from CDN for client use (which may save lots of users time, because they'll already have it cached locally).
I recommend that all your reusable components export factories and take a React instance as a dependency. It's really easy. A regular pure component looks like this:
export default (props) => <h1>{ props.title }</h1>;
To add the factory wrapper for React injection, just insert another arrow function with a React
parameter:
export default React => (props) => <h1>{ props.title }</h1>;
If you're still confused, this desugars to this ordinary ES5:
"use strict";
module.exports = function (React) {
return function (props) {
return React.createElement(
"h1",
null,
props.title
);
};
};
Yeah. Arrow functions rock.
In case you blinked and missed it, the ES6 factory again:
export default React => (props) => <h1>{ props.title }</h1>;
As you can see, React is a parameter, but it doesn't get explicitly mentioned anywhere in the rest of the line... and there are no other lines. So why do we need it?
Remember that JSX is not real DOM markup. There's a compile step that transforms the JSX code into this:
React.createElement(
"h1",
null,
props.title
);
That compiled output uses React, and expects it to be available inside the component scope, so you need to pass React
in, even though it's not obvious that it's being used.
I recommend Test Driven Development (TDD). Write your tests first. Learn how to write unit tests: Read 5 Questions Every Unit Test Must Answer.
Unit testing React components is a lot easier than it sounds. Let's look at the imports for the title
example in test/components/title/index.js
:
import React from 'react';
import reactDom from 'react-dom/server';
import test from 'tape';
import dom from 'cheerio';
The first line pulls in React, which you'll need to pass into the component factory. As we already mentioned, it's also required for the JSX to work.
import reactDom from 'react-dom/server'
React 0.14 split the DOM utilities out of the main React package. There are several reasons for this change. One of the more important reasons is that React is not always used to render HTML DOM. For instance, Netflix uses it to render to an in-house rendering library called Gibbon, and Facebook has another framework called React Native, which lets you build native user interfaces on mobile using JavaScript, sharing much of the same code and architecture with your web and server apps.
So, react's DOM utilities now live in react-dom
, which is split in two:
react-dom/server
react-dom/client
import test from 'tape';
Tape is a great testrunner because it keeps everything very simple. For details, read Why I Use Tape Instead of Mocha, and So Should You.
import dom from 'cheerio';
Cheerio is a jQuery-like API for querying and manipulating DOM nodes. If you know jQuery, you know Cheerio.
I use it for testing React component outputs. Much better than the peculiarly named .findRenderedDOMComponentWithClass()
and .scryRenderedDOMComponentsWithClass()
(I swear, I'm not making these up).
It does not need JSDom. It does not need Selenium web driver. It does not need a browser. Not even PhantomJS. Your DOM is just DOM. Save the browser wrangling for your critical path functional tests. Keep your component unit tests simple.
"Simplicity is a feature." ~ Jafar Husain (Netflix, TC39)
An online course series for application developers. Ready to jump in? Learn more.