diff --git a/README.md b/README.md index c73411c..8d11f5b 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,22 @@ React.BackboneMixin({ }); ``` +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() { + 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. diff --git a/__tests__/react.backbone-test.js b/__tests__/react.backbone-test.js index 778d424..49a79d8 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"); @@ -69,14 +69,57 @@ describe('react.backbone', function() { expect(header.getDOMNode().textContent).toEqual('David 80'); }); + }); + + describe("with shouldComponentUpdate", 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")}

+
+ ); + } + }); + + 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); + user.set("age", "100"); + + var header = TestUtils.findRenderedDOMComponentWithTag(userView, 'h1'); + expect(header.getDOMNode().textContent).toEqual('Mehdi 80'); + + userView.forceUpdate() + expect(header.getDOMNode().textContent).toEqual('Mehdi 100'); + }); + + + + }); }); 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 +132,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 +145,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 +155,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(){ + return false; + }, + render: function() { + var usersList = this.getCollection().map(function(user, index) { + return
  • {user.get("name")}
  • ; + }); + + return ( + + ); + } + }); + + 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 +211,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 +225,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 +255,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 +281,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 +313,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 +346,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 +356,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 +374,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 +384,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 +392,4 @@ describe('react.backbone', function() { }); }); - }); \ No newline at end of file diff --git a/react.backbone.js b/react.backbone.js index 7077751..42249ac 100644 --- a/react.backbone.js +++ b/react.backbone.js @@ -25,6 +25,13 @@ updateScheduler: _.identity }; + var updatable = function (component){ + if (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); } }); @@ -79,7 +86,7 @@ unsubscribe(this, modelOrCollection(this.props)); subscribe(this, modelOrCollection(nextProps), customChangeOptions); - + if (typeof this.componentWillChangeModel === 'function') { this.componentWillChangeModel(); } @@ -98,6 +105,7 @@ componentWillUnmount: function() { // Ensure that we clean up any dangling references when the component is destroyed. unsubscribe(this, modelOrCollection(this.props)); + } }; };