diff --git a/derivers.js b/derivers.js new file mode 100644 index 0000000..1f01455 --- /dev/null +++ b/derivers.js @@ -0,0 +1,43 @@ +import canDiff from 'can-diff'; + +function controlled(nextProps) { + // always use the latest props, overwiting existing data + return nextProps; +} + +function uncontrolled(nextProps, lastProps) { + // use props on initial render (when lastProps is null) + if (lastProps === null) { + return nextProps; + } + + // otherwise do nothing + return null; +} + +function changes(nextProps, lastProps) { + // use props on initial render (when lastProps is null) + if (lastProps === null) { + return nextProps; + } + + const patches = canDiff.map(lastProps, nextProps); + if (!patches.length) { + return null; + } + + const diff = {}; + for (const { key, type, value } of patches) { + if (type === 'set' || type === 'add') { + diff[key] = value; + } + } + + return diff; +} + +export { + controlled, + uncontrolled, + changes, +}; diff --git a/lib/observable-component.js b/lib/observable-component.js index 602df07..983ed47 100644 --- a/lib/observable-component.js +++ b/lib/observable-component.js @@ -5,14 +5,15 @@ import canKey from 'can-key'; import { Object as ObservableClass } from 'can-observe'; import ObserverComponent from './observer-component'; import { createObservablePropsComponent } from './observable-props-component'; +import { changes as deriveUpdatesChanges } from '../derivers'; class ObservableComponent extends ObserverComponent { static ObservableClass = ObservableClass - static mapProps = (nextProps) => nextProps; + static deriveUpdates = deriveUpdatesChanges; - static getDerivedStateFromProps(nextProps, { observer, observable, mapProps, lastProps }) { + static getDerivedStateFromProps(nextProps, { observer, observable, deriveUpdates, lastProps }) { if (nextProps !== lastProps) { - updateObservableWithProps(observable, mapProps(nextProps, lastProps), observer); + updateObservableWithProps(observable, deriveUpdates(nextProps, lastProps), observer); } return { @@ -34,7 +35,8 @@ class ObservableComponent extends ObserverComponent { this.state = { observable: new this.constructor.ObservableClass(), observer: this.observer, - mapProps: this.constructor.mapProps, + deriveUpdates: this.constructor.deriveUpdates, + lastProps: null, }; } @@ -109,10 +111,16 @@ function updateObservableWithProps(observable, newProps, observer) { }); } -function createComponent(ObservableClass, { mapProps = props => props } = {}) { +function createComponent(ObservableClass, { deriveUpdates } = {}) { + if (deriveUpdates) { + return class YlemObservable extends ObservableComponent { + static ObservableClass = ObservableClass + static deriveUpdates = deriveUpdates + }; + } + return class YlemObservable extends ObservableComponent { static ObservableClass = ObservableClass - static mapProps = mapProps }; } diff --git a/test/connect.js b/test/connect.js index 841a15f..b36e080 100644 --- a/test/connect.js +++ b/test/connect.js @@ -219,7 +219,7 @@ QUnit.module('@connect with ObserveObject', () => { foo = 'foo' } - @connect(ViewModel, { mapProps: props => ({ props }) }) + @connect(ViewModel, { deriveUpdates: props => ({ props }) }) class TestComponent extends Component { static propTypes = { foo: PropTypes.string.isRequired, @@ -261,7 +261,7 @@ QUnit.module('@connect with ObserveObject', () => { }).isRequired, }; - const ConnectedTestComponent = connect(ViewModel, { mapProps: props => ({ props }) })(TestComponent); + const ConnectedTestComponent = connect(ViewModel, { deriveUpdates: props => ({ props }) })(TestComponent); if (process.env.NODE_ENV !== 'test-prod') { supportsFunctionName && assert.equal(ConnectedTestComponent.name, 'YlemConnected(TestComponent)', 'returned component is properly named'); @@ -410,14 +410,12 @@ QUnit.module('@connect with ObserveObject', () => { child11ViewModel.value = 'i'; assert.equal(getTextFromElement(parentDiv), 'ab!cd@e#fghi', 'ab!cd@e#fghi'); - // Note: because parent re-rendered, child props overwrite change values in observable instance - parentViewModel.prop0 = 'A'; - assert.equal(getTextFromElement(parentDiv), '01A23@4#5678', '01A23@4#5678'); + assert.equal(getTextFromElement(parentDiv), 'abAcd@e#fghi', 'abAcd@e#fghi'); parentViewModel.prop1 = 'B'; - assert.equal(getTextFromElement(parentDiv), '01A23B4#5678', '01A23B4#5678'); + assert.equal(getTextFromElement(parentDiv), 'abAcdBe#fghi', 'abAcdBe#fghi'); parentViewModel.prop2 = 'C'; - assert.equal(getTextFromElement(parentDiv), '01A23B4C5678', '01A23B4C5678'); + assert.equal(getTextFromElement(parentDiv), 'abAcdBeCfghi', 'abAcdBeCfghi'); }); });