From e7d97fdf695c1de9a1c1b9cdfa08f060b5af1cd2 Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Sun, 10 May 2015 16:06:16 +0100 Subject: [PATCH 1/8] Added support of shouldComponentUpdate --- react.backbone.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/react.backbone.js b/react.backbone.js index 7077751..e86ff61 100644 --- a/react.backbone.js +++ b/react.backbone.js @@ -50,6 +50,13 @@ modelOrCollection.off(null, null, component); }; + var updatable = function (component){ + if (component.shouldComponentUpdate !== undefined && component.shouldComponentUpdate !== null){ + return component.shouldComponentUpdate(); + } + return true; + } + React.BackboneMixin = function(optionsOrPropName, customChangeOptions) { var propName, modelOrCollection; if (typeof optionsOrPropName === "object") { @@ -68,8 +75,11 @@ return { componentDidMount: function() { - // Whenever there may be a change in the Backbone data, trigger a reconcile. - subscribe(this, modelOrCollection(this.props), customChangeOptions); + if (updatable(this)){ + // Whenever there may be a change in the Backbone data, trigger a reconcile. + subscribe(this, modelOrCollection(this.props), customChangeOptions); + } + }, componentWillReceiveProps: function(nextProps) { @@ -77,9 +87,10 @@ return; } - unsubscribe(this, modelOrCollection(this.props)); - subscribe(this, modelOrCollection(nextProps), customChangeOptions); - + if (updatable(this)){ + unsubscribe(this, modelOrCollection(this.props)); + subscribe(this, modelOrCollection(nextProps), customChangeOptions); + } if (typeof this.componentWillChangeModel === 'function') { this.componentWillChangeModel(); } @@ -97,7 +108,9 @@ componentWillUnmount: function() { // Ensure that we clean up any dangling references when the component is destroyed. - unsubscribe(this, modelOrCollection(this.props)); + if (updatable(this)){ + unsubscribe(this, modelOrCollection(this.props)); + } } }; }; From 00d68b59f6879b1128f030a6f1042d8dee8c2625 Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 13:55:43 +0100 Subject: [PATCH 2/8] added shouldComponentUpdate documentation --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index c73411c..5893448 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,22 @@ React.BackboneMixin({ }); ``` +If in you need same cases to control whatever a change should re-render the view or not. +```javascript +var UserViewComponent = React.createBackboneClass({ + shouldComponentUpdate: function() { + return this.getModel().previous('name') != this.getModel().get('name'); + }, + render: function() { + return ( +
+

{this.getModel().get("name")}

+
+ ); + } +}); +``` + If your Collection or Model class does not inherit directly from Backbone.Model or Backbone.Collection, you may customize the behavior on a library level by overriding the React.BackboneMixin.ConsiderAsCollection function. From c5d7301b368d4d09666384362f99e2e72fc2a72f Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 13:56:47 +0100 Subject: [PATCH 3/8] fixed Trigger Update --- react.backbone.js | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/react.backbone.js b/react.backbone.js index e86ff61..253098c 100644 --- a/react.backbone.js +++ b/react.backbone.js @@ -25,6 +25,13 @@ updateScheduler: _.identity }; + var updatable = function (component){ + if (component.shouldComponentUpdate !== undefined && component.shouldComponentUpdate !== null){ + return component.shouldComponentUpdate(); + } + return true; + } + var subscribe = function(component, modelOrCollection, customChangeOptions) { if (!modelOrCollection) { return; @@ -33,7 +40,7 @@ var behavior = React.BackboneMixin.ConsiderAsCollection(modelOrCollection) ? collectionBehavior : modelBehavior; var triggerUpdate = behavior.updateScheduler(function() { - if (component.isMounted()) { + if (component.isMounted() && updatable(component)) { (component.onModelChange || component.forceUpdate).call(component); } }); @@ -50,13 +57,6 @@ modelOrCollection.off(null, null, component); }; - var updatable = function (component){ - if (component.shouldComponentUpdate !== undefined && component.shouldComponentUpdate !== null){ - return component.shouldComponentUpdate(); - } - return true; - } - React.BackboneMixin = function(optionsOrPropName, customChangeOptions) { var propName, modelOrCollection; if (typeof optionsOrPropName === "object") { @@ -75,11 +75,8 @@ return { componentDidMount: function() { - if (updatable(this)){ - // Whenever there may be a change in the Backbone data, trigger a reconcile. - subscribe(this, modelOrCollection(this.props), customChangeOptions); - } - + // Whenever there may be a change in the Backbone data, trigger a reconcile. + subscribe(this, modelOrCollection(this.props), customChangeOptions); }, componentWillReceiveProps: function(nextProps) { @@ -87,10 +84,9 @@ return; } - if (updatable(this)){ - unsubscribe(this, modelOrCollection(this.props)); - subscribe(this, modelOrCollection(nextProps), customChangeOptions); - } + unsubscribe(this, modelOrCollection(this.props)); + subscribe(this, modelOrCollection(nextProps), customChangeOptions); + if (typeof this.componentWillChangeModel === 'function') { this.componentWillChangeModel(); } @@ -108,9 +104,8 @@ componentWillUnmount: function() { // Ensure that we clean up any dangling references when the component is destroyed. - if (updatable(this)){ - unsubscribe(this, modelOrCollection(this.props)); - } + unsubscribe(this, modelOrCollection(this.props)); + } }; }; From fbbe8e427ee344d897aa2ad5f9d2734f081d1e2c Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 13:57:06 +0100 Subject: [PATCH 4/8] Fixed tests and add shouldComponentUpdate tests --- __tests__/react.backbone-test.js | 99 +++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/__tests__/react.backbone-test.js b/__tests__/react.backbone-test.js index 778d424..9ceadce 100644 --- a/__tests__/react.backbone-test.js +++ b/__tests__/react.backbone-test.js @@ -20,23 +20,23 @@ describe('react.backbone', function() { it('renders model as-is', function() { var user = new Backbone.Model({name: "Clay"}); - var userViewRef = UserView({model: user}); + var userViewRef = React.createFactory(UserView)({model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); expect(header.getDOMNode().textContent).toEqual('Clay'); }); - + it('renders model after changes to property', function() { var user = new Backbone.Model({name: "Clay"}); - var userViewRef = UserView({model: user}); + var userViewRef = React.createFactory(UserView)({model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); user.set("name", "David"); var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); expect(header.getDOMNode().textContent).toEqual('David'); }); - + describe("with changeOptions", function() { var UserView = React.createBackboneClass({ changeOptions: "change:name", @@ -51,7 +51,7 @@ describe('react.backbone', function() { it('doesnt render if other property is changed', function() { var user = new Backbone.Model({name: "Clay", age: "80"}); - var userViewRef = UserView({model: user}); + var userViewRef = React.createFactory(UserView)({model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); user.set("age", "60"); @@ -61,7 +61,7 @@ describe('react.backbone', function() { it('does render if valid property is changed', function() { var user = new Backbone.Model({name: "Clay", age: "80"}); - var userViewRef = UserView({model: user}); + var userViewRef = React.createFactory(UserView)({model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); user.set("name", "David"); @@ -70,13 +70,38 @@ describe('react.backbone', function() { }); }); + + describe("with shouldComponentUpdate", function() { + it('doesnt auto-update if shouldComponentUpdate is false', function() { + var UserView = React.createBackboneClass({ + shouldComponentUpdate: function(){ + return this.getModel().previous('name') != this.getModel().get('name'); + }, + render: function() { + return ( +
+

{this.getModel().get("name")} {this.getModel().get("age")}

+
+ ); + } + }); + var user = new Backbone.Model({name: "Mehdi", age: "80"}); + var userViewRef = React.createFactory(UserView)({model: user}); + var userView = TestUtils.renderIntoDocument(userViewRef); + user.set("age", "100"); + + var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); + expect(header.getDOMNode().textContent).toEqual('Mehdi 80'); + }); + + }); }); describe("with :collection key", function() { var UsersListView = React.createBackboneClass({ render: function() { - var usersList = this.getCollection().map(function(user) { - return
  • {user.get("name")}
  • ; + var usersList = this.getCollection().map(function(user, index) { + return
  • {user.get("name")}
  • ; }); return ( @@ -89,7 +114,7 @@ describe('react.backbone', function() { it('renders collection as-is', function() { var usersList = new Backbone.Collection([{name: "Clay"}, {name: "David"}]); - var usersListViewRef = UsersListView({collection: usersList}); + var usersListViewRef = React.createFactory(UsersListView)({collection: usersList}); var usersListView = TestUtils.renderIntoDocument(usersListViewRef); jest.runOnlyPendingTimers(); @@ -102,7 +127,7 @@ describe('react.backbone', function() { it('renders collection on adding', function() { var usersList = new Backbone.Collection([{name: "Clay"}, {name: "David"}]); - var usersListViewRef = UsersListView({collection: usersList}); + var usersListViewRef = React.createFactory(UsersListView)({collection: usersList}); var usersListView = TestUtils.renderIntoDocument(usersListViewRef); usersList.add({name: "Jack"}); @@ -112,6 +137,41 @@ describe('react.backbone', function() { expect(list.getDOMNode().childNodes.length).toEqual(3); expect(list.getDOMNode().childNodes[2].textContent).toEqual("Jack"); }); + + describe("with shouldComponentUpdate", function() { + it('doesnt auto-update if shouldComponentUpdate is false', function() { + + var UsersListView = React.createBackboneClass({ + shouldComponentUpdate: function(nextProps, nextState){ + return false; + }, + render: function() { + var usersList = this.getCollection().map(function(user, index) { + return
  • {user.get("name")}
  • ; + }); + + return ( +
      + { usersList } +
    + ); + } + }); + + var usersList = new Backbone.Collection([{name: "Mehdi"}, {name: "David"}]); + var usersListViewRef = React.createFactory(UsersListView)({collection: usersList}); + var usersListView = TestUtils.renderIntoDocument(usersListViewRef); + usersList.add({name: "Jack"}); + + jest.runOnlyPendingTimers(); + + var list = TestUtils.findRenderedDOMComponentWithTag(usersListView, 'ul'); + expect(list.getDOMNode().childNodes.length).toEqual(2); + expect(list.getDOMNode().childNodes[2]).toEqual(null); + + }); + + }); }); describe("with mixins", function() { @@ -133,7 +193,7 @@ describe('react.backbone', function() { it("should render mixins as-is", function() { var user = new Backbone.Model({name: "Clay"}); var wall = new Backbone.Model({post_count: 5}); - var profileViewRef = ProfileView({user: user, wall: wall}); + var profileViewRef = React.createFactory(ProfileView)({user: user, wall: wall}); var profileView = TestUtils.renderIntoDocument(profileViewRef); var header = TestUtils.findRenderedDOMComponentWithTag(profileView, 'h1'); @@ -147,7 +207,7 @@ describe('react.backbone', function() { it("should re-render if either mixin model is changed", function() { var user = new Backbone.Model({name: "Clay"}); var wall = new Backbone.Model({post_count: 5}); - var profileViewRef = ProfileView({user: user, wall: wall}); + var profileViewRef = React.createFactory(ProfileView)({user: user, wall: wall}); var profileView = TestUtils.renderIntoDocument(profileViewRef); user.set("name", "David"); @@ -177,7 +237,7 @@ describe('react.backbone', function() { }); it("should use that prop", function() { var user = new Backbone.Model({name: "Clay"}); - var userViewRef = UserView({user_model: user}); + var userViewRef = React.createFactory(UserView)({user_model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); @@ -203,7 +263,7 @@ describe('react.backbone', function() { }); it("should use that event", function() { var user = new Backbone.Model({name: "Clay", age: 25}); - var userViewRef = UserView({user_model: user}); + var userViewRef = React.createFactory(UserView)({user_model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); var header; @@ -235,7 +295,7 @@ describe('react.backbone', function() { }); it("should use that return value", function() { var user = new Backbone.Model({name: "Clay"}); - var userViewRef = UserView({user_model: user}); + var userViewRef = React.createFactory(UserView)({user_model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); @@ -268,7 +328,7 @@ describe('react.backbone', function() { expect(object).toEqual(users); return true; }; - var userViewRef = UserView({ collection: users }); + var userViewRef = React.createFactory(UserView)({collection: users}); TestUtils.renderIntoDocument(userViewRef); }); it("should pass model to config function for test", function () { @@ -278,7 +338,7 @@ describe('react.backbone', function() { expect(object).toEqual(user); return false; }; - var userViewRef = UserView({ model: user }); + var userViewRef = React.createFactory(UserView)({model: user}); TestUtils.renderIntoDocument(userViewRef); }); @@ -296,7 +356,7 @@ describe('react.backbone', function() { React.BackboneMixin.ConsiderAsCollection = function (object) { return false; }; - var userViewRef = UserView({ model: usersModel }); + var userViewRef = React.createFactory(UserView)({model: usersModel}); TestUtils.renderIntoDocument(userViewRef); }); @@ -306,7 +366,7 @@ describe('react.backbone', function() { React.BackboneMixin.ConsiderAsCollection = function (object) { return true; }; - var userViewRef = UserView({ collection: usersCollection }); + var userViewRef = React.createFactory(UserView)({collection: usersCollection}); TestUtils.renderIntoDocument(userViewRef); }); @@ -314,5 +374,4 @@ describe('react.backbone', function() { }); }); - }); \ No newline at end of file From 2c4287d70b4df4429bba57c38daa117bd06471c7 Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 13:57:53 +0100 Subject: [PATCH 5/8] fixed a little mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5893448..b3ba50c 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ React.BackboneMixin({ }); ``` -If in you need same cases to control whatever a change should re-render the view or not. +If in same cases you need to control whatever a change should re-render the view or not. ```javascript var UserViewComponent = React.createBackboneClass({ shouldComponentUpdate: function() { From ddfab838dc486bb75bae91b46d64a33896ba0279 Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 13:59:15 +0100 Subject: [PATCH 6/8] fixed a little mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3ba50c..8d11f5b 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ React.BackboneMixin({ }); ``` -If in same cases you need to control whatever a change should re-render the view or not. +If in some cases you need to control whatever a change should re-render the view or not: ```javascript var UserViewComponent = React.createBackboneClass({ shouldComponentUpdate: function() { From e693bd557581390c7d21d58e2e9de466e6d5dd81 Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 22:58:14 +0100 Subject: [PATCH 7/8] removed an unnecessary verification --- react.backbone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react.backbone.js b/react.backbone.js index 253098c..42249ac 100644 --- a/react.backbone.js +++ b/react.backbone.js @@ -26,7 +26,7 @@ }; var updatable = function (component){ - if (component.shouldComponentUpdate !== undefined && component.shouldComponentUpdate !== null){ + if (component.shouldComponentUpdate !== null){ return component.shouldComponentUpdate(); } return true; From 05ee27173b50277dfd0a29f5a4c10e37e156fec1 Mon Sep 17 00:00:00 2001 From: Mehdi Sakout Date: Tue, 12 May 2015 22:58:35 +0100 Subject: [PATCH 8/8] added new test for forceUpdate --- __tests__/react.backbone-test.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/__tests__/react.backbone-test.js b/__tests__/react.backbone-test.js index 9ceadce..49a79d8 100644 --- a/__tests__/react.backbone-test.js +++ b/__tests__/react.backbone-test.js @@ -72,8 +72,7 @@ describe('react.backbone', function() { }); describe("with shouldComponentUpdate", function() { - it('doesnt auto-update if shouldComponentUpdate is false', function() { - var UserView = React.createBackboneClass({ + var UserView = React.createBackboneClass({ shouldComponentUpdate: function(){ return this.getModel().previous('name') != this.getModel().get('name'); }, @@ -84,7 +83,20 @@ describe('react.backbone', function() { ); } - }); + }); + + it('doesnt auto-update if shouldComponentUpdate is false', function() { + + var user = new Backbone.Model({name: "Mehdi", age: "80"}); + var userViewRef = React.createFactory(UserView)({model: user}); + var userView = TestUtils.renderIntoDocument(userViewRef); + user.set("age", "100"); + + var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); + expect(header.getDOMNode().textContent).toEqual('Mehdi 80'); + }); + + it('forces update even if shouldComponentUpdate is false', function() { var user = new Backbone.Model({name: "Mehdi", age: "80"}); var userViewRef = React.createFactory(UserView)({model: user}); var userView = TestUtils.renderIntoDocument(userViewRef); @@ -92,8 +104,14 @@ describe('react.backbone', function() { var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); expect(header.getDOMNode().textContent).toEqual('Mehdi 80'); + + userView.forceUpdate() + expect(header.getDOMNode().textContent).toEqual('Mehdi 100'); }); + + + }); }); @@ -142,7 +160,7 @@ describe('react.backbone', function() { it('doesnt auto-update if shouldComponentUpdate is false', function() { var UsersListView = React.createBackboneClass({ - shouldComponentUpdate: function(nextProps, nextState){ + shouldComponentUpdate: function(){ return false; }, render: function() {