├── .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 | --------------------------------------------------------------------------------