Skip to content

Commit

Permalink
Immutable.js to check if stateful comps should render.
Browse files Browse the repository at this point in the history
  • Loading branch information
n1k0 committed Mar 19, 2016
1 parent bafd5de commit 15bdd17
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 18 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@
"extract-text-webpack-plugin": "^0.9.1",
"gh-pages": "^0.4.0",
"html": "0.0.10",
"immutable": "^3.7.6",
"jsdom": "^7.2.1",
"mocha": "^2.3.0",
"react-addons-shallow-compare": "^0.14.3",
"react-addons-test-utils": "^0.14.3",
"react-codemirror": "^0.2.3",
"react-transform-catch-errors": "^1.0.0",
Expand Down
28 changes: 21 additions & 7 deletions playground/app.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { Component } from "react";
import { render } from "react-dom";
import shallowCompare from "react-addons-shallow-compare";
import Codemirror from "react-codemirror";
import "codemirror/mode/javascript/javascript";

import { shouldRender } from "../src/utils";
import { samples } from "./samples";
import Form from "../src";

import Immutable from "immutable";

import "codemirror/lib/codemirror.css";
import "./styles.css";

Expand Down Expand Up @@ -39,7 +41,7 @@ class Editor extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
return shouldRender(this, nextProps, nextState);
}

onCodeChange(code) {
Expand Down Expand Up @@ -78,7 +80,7 @@ class Selector extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
return shouldRender(this, nextProps, nextState);
}

onClick(label, sampleData, event) {
Expand Down Expand Up @@ -117,7 +119,7 @@ class App extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
return shouldRender(this, nextProps, nextState);
}

load(data) {
Expand All @@ -126,6 +128,18 @@ class App extends Component {
_ => this.setState({...data, form: true}));
}

onSchemaChange(schema) {
this.setState({schema});
}

onUISchemaChange(uiSchema) {
this.setState({uiSchema});
}

onFormDataChange(formData) {
this.setState({formData});
}

render() {
return (
<div className="container-fluid">
Expand All @@ -136,17 +150,17 @@ class App extends Component {
<div className="col-sm-6">
<Editor title="JSONSchema"
code={toJson(this.state.schema)}
onChange={schema => this.setState({schema})} />
onChange={this.onSchemaChange.bind(this)} />
<div className="row">
<div className="col-sm-6">
<Editor title="UISchema"
code={toJson(this.state.uiSchema)}
onChange={uiSchema => this.setState({uiSchema})} />
onChange={this.onUISchemaChange.bind(this)} />
</div>
<div className="col-sm-6">
<Editor title="formData"
code={toJson(this.state.formData)}
onChange={formData => this.setState({formData})} />
onChange={this.onFormDataChange.bind(this)} />
</div>
</div>
</div>
Expand Down
5 changes: 2 additions & 3 deletions src/components/Form.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { Component, PropTypes } from "react";
import { Validator } from "jsonschema";
import shallowCompare from "react-addons-shallow-compare";

import SchemaField from "./fields/SchemaField";
import TitleField from "./fields/TitleField";
import { getDefaultFormState } from "../utils";
import { getDefaultFormState, shouldRender } from "../utils";
import ErrorList from "./ErrorList";

export default class Form extends Component {
Expand Down Expand Up @@ -35,7 +34,7 @@ export default class Form extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
return shouldRender(this, nextProps, nextState);
}

validate(formData, schema) {
Expand Down
6 changes: 3 additions & 3 deletions src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { Component, PropTypes } from "react";
import shallowCompare from "react-addons-shallow-compare";

import {
getDefaultFormState,
isMultiSelect,
optionsList,
retrieveSchema
retrieveSchema,
shouldRender
} from "../../utils";
import SelectWidget from "./../widgets/SelectWidget";

Expand Down Expand Up @@ -33,7 +33,7 @@ class ArrayField extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
return shouldRender(this, nextProps, nextState);
}

get itemTitle() {
Expand Down
6 changes: 3 additions & 3 deletions src/components/fields/ObjectField.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Component, PropTypes } from "react";
import shallowCompare from "react-addons-shallow-compare";

import {
getDefaultFormState,
orderProperties,
retrieveSchema
retrieveSchema,
shouldRender
} from "../../utils";


Expand All @@ -28,7 +28,7 @@ class ObjectField extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
return shouldRender(this, nextProps, nextState);
}

isRequired(name) {
Expand Down
22 changes: 22 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Immutable from "immutable";

import PasswordWidget from "./components/widgets/PasswordWidget";
import RadioWidget from "./components/widgets/RadioWidget";
import UpDownWidget from "./components/widgets/UpDownWidget";
Expand Down Expand Up @@ -186,3 +188,23 @@ export function retrieveSchema(schema, definitions={}) {
// Retrieve the referenced schema definition.
return findSchemaDefinition(schema.$ref, definitions);
}

function flattenFunctions(obj) {
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
if (typeof value === "function") {
acc[key] = value.toString();
} else if (value !== null && typeof value === "object") {
acc[key] = flattenFunctions(value);
} else {
acc[key] = value;
}
return acc;
}, {});
}

export function shouldRender(comp, nextProps, nextState) {
const immu = (obj) => Immutable.fromJS(flattenFunctions(obj));
return !immu(comp.props).equals(immu(nextProps)) ||
!immu(comp.state).equals(immu(nextState));
}
14 changes: 14 additions & 0 deletions test/Form_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,4 +493,18 @@ describe("Form", () => {
});
});
});

describe("Performances", () => {
it("should not render if new props are equivalent", () => {
const schema = {type: "string"};
const uiSchema = {};

const {comp} = createComponent({schema, uiSchema});
sandbox.stub(comp, "render").returns(<div/>);

comp.componentWillReceiveProps({schema});

sinon.assert.notCalled(comp.render);
});
});
});
58 changes: 57 additions & 1 deletion test/utils_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
getDefaultFormState,
isMultiSelect,
mergeObjects,
retrieveSchema
retrieveSchema,
shouldRender
} from "../src/utils";


Expand Down Expand Up @@ -282,4 +283,59 @@ describe("utils", () => {
expect(retrieveSchema(schema, definitions)).eql(address_definition);
});
});

describe("shouldRender", () => {
const initial = {props: {myProp: 1}, state: {myState: 1}};

it("should detect equivalent props and state", () => {
expect(shouldRender(
initial,
{myProp: 1},
{myState: 1}
)).eql(false);
});

it("should detect diffing props", () => {
expect(shouldRender(
initial,
{myProp: 2},
{myState: 1}
)).eql(true);
});

it("should detect diffing state", () => {
expect(shouldRender(
initial,
{myProp: 1},
{myState: 2}
)).eql(true);
});

it("should handle equivalent function prop", () => {
const fn = () => {};
expect(shouldRender(
{props: {myProp: fn}, state: {myState: 1}},
{myProp: fn},
{myState: 1}
)).eql(false);
});

it("should handle equivalent function prop with diff identities", () => {
expect(shouldRender(
{props: {myProp: () => {}}, state: {myState: 1}},
{myProp: () => {}},
{myState: 1}
)).eql(false);
});

it("should handle equivalent bound function prop", () => {
const ctx = {};
const fn = function(){};
expect(shouldRender(
{props: {myProp: fn.bind(ctx)}, state: {myState: 1}},
{myProp: fn.bind(ctx)},
{myState: 1}
)).eql(false);
});
});
});

0 comments on commit 15bdd17

Please sign in to comment.