├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── modules
└── index.js
├── package.json
└── test
└── main.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015" ]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | index.js
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .*
3 | *.json
4 | *.md
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Thomas Roch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-stateless
2 |
3 | Helpers to write stateless functional components in React.
4 |
5 | > Write stateless functional components in React with lifecycle methods as pure functions!
6 |
7 |
8 | ### ReactStateless.createClass(specification)
9 |
10 | `specification` can be a __[stateless render function](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components)__
11 | or an __object containing pure stateless lifecycle functions__.
12 |
13 | #### Example: component as a function
14 |
15 | ```javascript
16 | import { React } from 'react';
17 | import { createClass } from 'react-stateless';
18 |
19 | function ComponentA(props) {
20 | return
{ props.name }
;
21 | }
22 |
23 | // React 0.14
24 | export default ComponentA
25 | // Or
26 | export default createClass(ComponentA);
27 | ```
28 |
29 | #### Example: component as an object
30 |
31 | ```javascript
32 | import { React } from 'react';
33 | import { createClass } from 'react-stateless';
34 |
35 | function shouldComponentUpdate(props, nextProps) {
36 | return props.name !== nextProps.name;
37 | }
38 |
39 | function render(props) {
40 | return { props.name }
;
41 | }
42 |
43 | export default createClass({shouldComponentUpdate, render})
44 | ```
45 |
46 | #### Supported properties
47 |
48 | - `propTypes`
49 | - `defaultProps`
50 | - `displayName` (automatically detected by if your component function or render function is named)
51 |
52 | #### Supported methods
53 |
54 | - `componentWillMount(props)`
55 | - `componentDidMount(props, refs)`
56 | - `componentWillReceiveProps(props, nextProps, refs)`
57 | - `shouldComponentUpdate(props, nextProps, refs)`
58 | - `componentWillUpdate(props, nextProps, refs)`
59 | - `componentDidUpdate(props, prevProps, refs)`
60 | - `componentWillUnmount(props, refs)`
61 |
--------------------------------------------------------------------------------
/modules/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function createClass(specification) {
4 | if (!specification instanceof Function && specification.render === undefined) {
5 | throw new Error('[ReactStateless.createClass(specification)] Not render function found. "specification" should be a render function or contain a render function.');
6 | }
7 |
8 | let render = function() {
9 | return specification instanceof Function ? specification(this.props) : specification.render(this.props);
10 | }
11 |
12 | let displayName;
13 | if (specification instanceof Function) displayName = specification.name || undefined;
14 | if (specification.render instanceof Function) displayName = specification.render.name || undefined;
15 |
16 | let supportedLifecycleMethods = [
17 | 'componentWillMount',
18 | 'componentDidMount',
19 | 'componentWillReceiveProps',
20 | 'shouldComponentUpdate',
21 | 'componentWillUpdate',
22 | 'componentDidUpdate',
23 | 'componentWillUnmount'
24 | ];
25 |
26 | let supportedProperties = [
27 | 'propTypes',
28 | 'defaultProps',
29 | 'getDefaultProps',
30 | 'displayName'
31 | ];
32 |
33 | let componentSpecification = { displayName };
34 |
35 | supportedLifecycleMethods
36 | .filter(method => specification[method] !== undefined)
37 | .forEach(method => {
38 | componentSpecification[method] = function() {
39 | const args = [this.props]
40 | .concat(arguments[0] === undefined ? [] : arguments[0])
41 | .concat(this.refs || []);
42 | return specification[method].apply(null, args);
43 | };
44 | });
45 |
46 | supportedProperties
47 | .filter(prop => specification[prop] !== undefined)
48 | .forEach(prop => componentSpecification[prop] = specification[prop]);
49 |
50 | componentSpecification.render = render;
51 |
52 |
53 | return React.createClass(componentSpecification);
54 | }
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-stateless",
3 | "version": "0.2.2",
4 | "description": "Helpers for writing stateless components in React",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "babel modules/index.js -o index.js",
8 | "test": "_mocha"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/troch/react-stateless.git"
13 | },
14 | "keywords": [
15 | "react",
16 | "stateless"
17 | ],
18 | "author": "Thomas Roch ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/troch/react-stateless/issues"
22 | },
23 | "homepage": "https://github.com/troch/react-stateless#readme",
24 | "devDependencies": {
25 | "assert": "^1.4.1",
26 | "babel-core": "^6.17.0",
27 | "babel-preset-es2015": "^6.16.0",
28 | "mocha": "^3.1.2",
29 | "react": "^15.3.2",
30 | "react-dom": "^15.3.2",
31 | "sinon": "^1.17.6"
32 | },
33 | "peerDependencies": {
34 | "react": "~0.14.0 || ^15.0.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/main.js:
--------------------------------------------------------------------------------
1 | var sinon = require('sinon');
2 | var assert = require('assert');
3 | var ReactStateless = require('../');
4 | var createClass = ReactStateless.createClass;
5 | var React = require('react');
6 | var ReactDOMServer = require('react-dom/server');
7 |
8 | require('mocha');
9 |
10 | describe('ReactStateless.createClass()', function() {
11 | it('should create a Component class from a render function', function() {
12 | var component = {
13 | render: function(props) {
14 | return React.createElement('div', {}, props.colour);
15 | }
16 | };
17 |
18 | var spy = sinon.spy(component, 'render');
19 | var componentClass = createClass(component.render);
20 |
21 | var props = {colour: 'blue'};
22 | var component = ReactDOMServer.renderToString(React.createElement(componentClass, props));
23 |
24 | assert(spy.calledWith(props));
25 | });
26 |
27 | it('should create a Component class from an object', function() {
28 | var component = {
29 | shouldComponentUpdate: function(props, nextProps) {
30 | return true;
31 | },
32 | render: function(props) {
33 | return React.createElement('div', {}, props.colour);
34 | }
35 | };
36 |
37 | var renderSpy = sinon.spy(component, 'render');
38 | var shouldUpdateSpy = sinon.spy(component, 'shouldComponentUpdate');
39 |
40 | var componentClass = createClass(component);
41 |
42 | var props = {colour: 'blue'};
43 | var element = React.createElement(componentClass, props);
44 | var renderedString = ReactDOMServer.renderToString(element);
45 |
46 | assert(renderSpy.calledWith(props));
47 | });
48 | });
49 |
--------------------------------------------------------------------------------