├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── package.json
├── spec
├── .eslintrc
└── immutableRenderSpec.js
└── src
├── immutableRenderDecorator.js
├── immutableRenderMixin.js
├── index.js
├── shallowEqualImmutable.js
└── shouldComponentUpdate.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb/base",
3 | "globals": {
4 | "describe": true,
5 | "it": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /lib
3 | *.log
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | examples
3 | .babelrc
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Clint Ayres
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | react-immutable-render-mixin
2 | ============================
3 |
4 | ## Users are urged to use [PureRenderMixin](http://facebook.github.io/react/docs/pure-render-mixin.html) with [facebook/immutable-js](https://github.com/facebook/immutable-js). If performance is still an issue an examination of your usage of Immutable.js should be your first path towards a solution. This library was created from experimentations with Immutable that were ultimately erroneous; improper usage of Immutable.js :hankey:. Users should be able to achieve maximum performance simply using PureRenderMixin.
5 |
6 | This library exposes 4 distinct options for immutable rendering:
7 |
8 | * Mixin for `React.createClass` support
9 | * HoC ( _decorator_ ) for `React.Component`
10 | * shouldComponentUpdate function used by the mixin and HoC
11 | * shallowEqualImmutable function to allow custom `shouldComponentUpdate` implementations
12 |
13 | This library when used as a mixin/decorator replaces the [PureRenderMixin](http://facebook.github.io/react/docs/pure-render-mixin.html) when using [facebook/immutable-js](https://github.com/facebook/immutable-js) library with [React](https://github.com/facebook/react)
14 |
15 | This Mixin and HoC implements `shouldComponentUpdate` method using prop and state equality with Immutable.is().
16 |
17 | We also expose the `shallowEqualImmutable` to allow developers to craft a custom `shouldComponentUpdate` method as needed.
18 |
19 | Installation
20 | ------------
21 |
22 | ```sh
23 | npm i react-immutable-render-mixin
24 | ```
25 |
26 | Usage as Mixin
27 | -----
28 |
29 | ```js
30 | import immutableRenderMixin from 'react-immutable-render-mixin';
31 |
32 | React.createClass({
33 | mixins: [immutableRenderMixin],
34 |
35 | render: function() {
36 | return
foo
;
37 | }
38 | });
39 | ```
40 |
41 | Usage as a HoC
42 | -----
43 |
44 | ```js
45 | import React from 'react';
46 | import { immutableRenderDecorator } from 'react-immutable-render-mixin';
47 |
48 | class Test extends React.Component {
49 | render() {
50 | return ;
51 | }
52 | }
53 |
54 | export default immutableRenderDecorator(Test);
55 | ```
56 |
57 | Usage as Decorator
58 | -----
59 |
60 | ```js
61 | import React from 'react';
62 | import { immutableRenderDecorator } from 'react-immutable-render-mixin';
63 |
64 | @immutableRenderDecorator
65 | class Test extends React.Component {
66 | render() {
67 | return ;
68 | }
69 | }
70 | ```
71 |
72 | Usage with default `shouldComponentUpdate`
73 | -----
74 |
75 | ```js
76 | import React from 'react';
77 | import { shouldComponentUpdate } from 'react-immutable-render-mixin';
78 |
79 | class Test extends React.Component {
80 | constructor(props) {
81 | super(props);
82 | this.shouldComponentUpdate = shouldComponentUpdate.bind(this);
83 | }
84 |
85 | render() {
86 | return ;
87 | }
88 | }
89 | ```
90 |
91 | Usage with a custom `shouldComponentUpdate`
92 | -----
93 |
94 | ```js
95 | import React from 'react';
96 | import { shallowEqualImmutable } from 'react-immutable-render-mixin';
97 |
98 | class Test extends React.Component {
99 | shouldComponentUpdate(nextProps, nextState) {
100 | return !shallowEqualImmutable(this.props, nextProps) || !shallowEqualImmutable(this.state, nextState);
101 | }
102 |
103 | render() {
104 | return ;
105 | }
106 | }
107 | ```
108 |
109 | Usage with <= ES5
110 | -----
111 |
112 | Exports:
113 |
114 | ```js
115 | var immutableRenderMixin = require('react-immutable-render-mixin').default;
116 |
117 | var immutableRenderDecorator = require('react-immutable-render-mixin').immutableRenderDecorator;
118 |
119 | var shallowEqualImmutable = require('react-immutable-render-mixin').shallowEqualImmutable;
120 |
121 | var shouldComponentUpdate = require('react-immutable-render-mixin').shouldComponentUpdate;
122 | ```
123 |
124 | Full Example:
125 |
126 | ```js
127 | var immutableRenderMixin = require('react-immutable-render-mixin').default;
128 |
129 | React.createClass({
130 | mixins: [immutableRenderMixin],
131 |
132 | render: function() {
133 | return foo
;
134 | }
135 | });
136 | ```
137 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-immutable-render-mixin",
3 | "version": "0.9.7",
4 | "description": "React PureRenderMixin replacement for immutable-js library",
5 | "homepage": "https://github.com/jurassix/react-immutable-render-mixin",
6 | "bugs": "https://github.com/jurassix/react-immutable-render-mixin/issues",
7 | "scripts": {
8 | "build": "npm run clean && npm run lint && npm run build:lib && npm run build:spec",
9 | "build:lib": "mkdirp lib && babel src -d lib",
10 | "build:spec": "mkdirp lib/spec && babel spec -d lib/spec",
11 | "test": "npm run build && mocha --recursive lib/spec",
12 | "clean": "rimraf lib",
13 | "lint": "eslint src && eslint spec",
14 | "prepublish": "npm run build"
15 | },
16 | "keywords": [
17 | "react",
18 | "mixin",
19 | "immutable-js",
20 | "immutability",
21 | "react-component"
22 | ],
23 | "main": "./lib/index.js",
24 | "author": "clint ayres",
25 | "license": "MIT",
26 | "peerDependencies": {
27 | "immutable": ">=2.0.10",
28 | "react": "*"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/jurassix/react-immutable-render-mixin.git"
33 | },
34 | "devDependencies": {
35 | "babel-cli": "^6.3.17",
36 | "babel-preset-es2015": "^6.3.13",
37 | "chai": "^3.4.1",
38 | "eslint": "^1.10.3",
39 | "eslint-config-airbnb": "^2.1.0",
40 | "mkdirp": "^0.5.1",
41 | "mocha": "^2.3.4",
42 | "react": "^0.14.7",
43 | "rimraf": "^2.4.4"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/spec/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "new-cap": [2, {"capIsNewExceptions": ["Immutable.List", "Immutable.Map", "Immutable.Set"]}]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/spec/immutableRenderSpec.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import Immutable from 'immutable';
3 |
4 | import immutableRenderMixin, { immutableRenderDecorator, shallowEqualImmutable } from '../index';
5 |
6 | import mixin from '../immutableRenderMixin';
7 | import decorator from '../immutableRenderDecorator';
8 | import shallowEqual from '../shallowEqualImmutable';
9 | import shouldComponentUpdate from '../shouldComponentUpdate';
10 |
11 | describe('ImmutableRenderMixin', () => {
12 | describe('exports', () => {
13 | it('should expose correct default export', () => {
14 | expect(immutableRenderMixin).to.deep.equal(mixin);
15 | });
16 | it('should expose decorator on export', () => {
17 | expect(immutableRenderDecorator).to.deep.equal(decorator);
18 | });
19 | it('should expose shallowEqual function on export', () => {
20 | expect(shallowEqualImmutable).to.deep.equal(shallowEqual);
21 | });
22 | it('should expose mixin as default for <= ES5', () => {
23 | const indexDefault = require('../index').default;
24 | expect(immutableRenderMixin).to.deep.equal(indexDefault);
25 | });
26 | it('should expose decorator on export for <= ES5', () => {
27 | const indexDecorator = require('../index').immutableRenderDecorator;
28 | expect(immutableRenderDecorator).to.deep.equal(indexDecorator);
29 | });
30 | it('should expose shallowEqual function for <= ES5', () => {
31 | const indexShallowEqual = require('../index').shallowEqualImmutable;
32 | expect(shallowEqualImmutable).to.deep.equal(indexShallowEqual);
33 | });
34 | });
35 | });
36 |
37 | describe('shallowEqualImmutable', () => {
38 | const equals = [
39 | ['string', 'string'],
40 | [true, true],
41 | [0, -0], // Immutable.is assumes 0 and -0 are the same value, matching the behavior of ES6 Map key equality.
42 | [Immutable.List([1, 2, 3]), Immutable.List([1, 2, 3])],
43 | [Immutable.Map({ a: 1, b: 1, c: 1 }), Immutable.Map({ a: 1, b: 1, c: 1 })],
44 | [Immutable.Set([NaN]), Immutable.Set([NaN])],
45 | ];
46 |
47 | const obj = { a: 1, b: 2, c: 3 };
48 | const map1 = Immutable.Map(obj);
49 | obj.a = 10;
50 | const map2 = Immutable.Map(obj);
51 | const notEquals = [
52 | ['string', 'other string'],
53 | [Immutable.List([1, 2, 3]), Immutable.List([3, 2, 1])],
54 | [Immutable.List([1]), Immutable.Set([1])],
55 | [map1, map2],
56 | ];
57 |
58 | it('can determines whether two arguments are the same value or two Immutable Iterable that have equivalent values', () => {
59 | equals.forEach(pair =>
60 | expect(shallowEqualImmutable(pair[0], pair[1])).to.be.true
61 | );
62 |
63 | notEquals.forEach(pair =>
64 | expect(shallowEqualImmutable(pair[0], pair[1])).to.be.false
65 | );
66 | });
67 |
68 | it('should return true if two arguments not equal but all their items do', () => {
69 | equals.forEach(pair => {
70 | const obj1 = { key: pair[0] };
71 | const obj2 = { key: pair[1] };
72 | expect(shallowEqualImmutable(obj1, obj2)).to.be.true; // eslint-disable-line no-unused-expressions
73 | });
74 |
75 | expect(shallowEqualImmutable({}, {})).to.be.true; // eslint-disable-line no-unused-expressions
76 |
77 | notEquals.forEach(pair => {
78 | const obj1 = { key: pair[0] };
79 | const obj2 = { key: pair[1] };
80 | expect(shallowEqualImmutable(obj1, obj2)).to.be.false; // eslint-disable-line no-unused-expressions
81 | });
82 | });
83 |
84 | it('should return false if two arguments has different number of keys', () => {
85 | const obj1 = { a: equals[0], b: equals[1] };
86 | const obj2 = { a: equals[0], b: equals[1], c: equals[2] };
87 | expect(shallowEqualImmutable(obj1, obj2)).to.be.false; // eslint-disable-line no-unused-expressions
88 | });
89 | });
90 |
91 | describe('shouldComponentUpdate', () => {
92 | it('can determines whether new props / states and current one are equivalent to', () => {
93 | const obj = {
94 | props: { a: Immutable.List([1, 2, 3]) },
95 | state: Immutable.Map({ a: 1, b: 2 }),
96 | shouldComponentUpdate,
97 | };
98 |
99 | expect( // eslint-disable-line no-unused-expressions
100 | obj.shouldComponentUpdate({ a: Immutable.List([1, 2, 3]) }, Immutable.Map({ a: 1, b: 2 }))
101 | ).to.be.false; // props and state are equal, should not update
102 |
103 | expect( // eslint-disable-line no-unused-expressions
104 | obj.shouldComponentUpdate({ a: Immutable.List([1, 2, 3]) }, Immutable.Map({ a: 1, b: 3 }))
105 | ).to.be.true;
106 | });
107 | });
108 |
109 | describe('immutableRenderDecorator', () => {
110 | it('can behavior like a HoC', () => {
111 | class TestComponent {}
112 | const Enhanced = immutableRenderDecorator(TestComponent);
113 | expect(Enhanced.prototype.shouldComponentUpdate).equal(shouldComponentUpdate);
114 | });
115 |
116 | it('should accept functional components', () => {
117 | const FunctionalComponent = () => null;
118 | const DecoratedComponent = immutableRenderDecorator(FunctionalComponent);
119 |
120 | expect(DecoratedComponent.prototype.shouldComponentUpdate).equal(shouldComponentUpdate);
121 | });
122 | });
123 |
--------------------------------------------------------------------------------
/src/immutableRenderDecorator.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import shouldComponentUpdate from './shouldComponentUpdate';
3 |
4 | /**
5 | * Makes the given component "pure".
6 | *
7 | * @param object Target Component.
8 | */
9 | export default function immutableRenderDecorator(Target) {
10 | class Wrapper extends Component {
11 | render() {
12 | return React.createElement(Target, this.props, this.props.children);
13 | }
14 | }
15 |
16 | Wrapper.prototype.shouldComponentUpdate = shouldComponentUpdate;
17 |
18 | return Wrapper;
19 | }
20 |
--------------------------------------------------------------------------------
/src/immutableRenderMixin.js:
--------------------------------------------------------------------------------
1 | import shouldComponentUpdate from './shouldComponentUpdate';
2 |
3 | export default {
4 | shouldComponentUpdate,
5 | };
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import shouldComponentUpdate from './shouldComponentUpdate';
2 | import shallowEqualImmutable from './shallowEqualImmutable';
3 | import immutableRenderMixin from './immutableRenderMixin';
4 | import immutableRenderDecorator from './immutableRenderDecorator';
5 |
6 | export {
7 | immutableRenderMixin as default,
8 | immutableRenderDecorator,
9 | shouldComponentUpdate,
10 | shallowEqualImmutable,
11 | };
12 |
--------------------------------------------------------------------------------
/src/shallowEqualImmutable.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 |
3 | const is = Immutable.is.bind(Immutable);
4 |
5 | export default function shallowEqualImmutable(objA, objB) {
6 | if (objA === objB || is(objA, objB)) {
7 | return true;
8 | }
9 |
10 | if (typeof objA !== 'object' || objA === null ||
11 | typeof objB !== 'object' || objB === null) {
12 | return false;
13 | }
14 |
15 | const keysA = Object.keys(objA);
16 | const keysB = Object.keys(objB);
17 |
18 | if (keysA.length !== keysB.length) {
19 | return false;
20 | }
21 |
22 | // Test for A's keys different from B.
23 | const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
24 | for (let i = 0; i < keysA.length; i++) {
25 | if (!bHasOwnProperty(keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
26 | return false;
27 | }
28 | }
29 |
30 | return true;
31 | }
32 |
--------------------------------------------------------------------------------
/src/shouldComponentUpdate.js:
--------------------------------------------------------------------------------
1 | import shallowEqualImmutable from './shallowEqualImmutable';
2 |
3 | export default function shouldComponentUpdate(nextProps, nextState) {
4 | return !shallowEqualImmutable(this.props, nextProps) || !shallowEqualImmutable(this.state, nextState);
5 | }
6 |
--------------------------------------------------------------------------------