From 80ac065d7174fb74609932e6e798eb17f8b0ef9c Mon Sep 17 00:00:00 2001 From: "William C. Johnson" Date: Mon, 27 Feb 2017 13:13:28 -0500 Subject: [PATCH] 0.3.3: - SubtreeMixin changes - Support dynamic mounting and unmounting - Add `subtreeReducer` field - Refactor internals --- CHANGELOG.md | 16 ++++++++++++++ README.md | 6 ++++++ src/subtree.coffee | 37 +++++++++++++++++---------------- src/test/02-subtree.test.coffee | 15 ++++++++++++- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 055dd93..0fc9b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # 0.3 +## 0.3.3 + +### SubtreeMixin changes + +The new mounting system enabled a cleanup of the `SubtreeMixin` internals, including the removal of the `didMount` monkey patch. This should not be a breaking change for those following the API, but may break code relying on subtree internals. + +- `SubtreeMixin` components now support dynamic mounting. (Meaning that the `SubtreeMixin` component can be dynamically mounted, not that its contents can change dynamically -- please use `redux-components-map` if you need that functionality.) + +- A new documented field, `this.subtreeReducer`, exists on all `SubtreeMixin` component instances. This is the reducer obtained by `combineReducers` over the subcomponents. Using this functionality, it is now possible to write custom reducers for `SubtreeMixin` components that fallback on the combined reducer when they don't understand an action. + +- The `__reducerMap` and `__originalDidMount` undocumented fields no longer exist. + +### Miscellaneous + +- The `Object.assign` polyfill has been removed. Your runtime must support `Object.assign` natively, or you must polyfill it yourself. + ## 0.3.2 ### Observable selector changes diff --git a/README.md b/README.md index bcab808..bb58ce9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # redux-components A component model for Redux state trees based on the React.js component model and other familiar design patterns from the React ecosystem. +## What's New + +- We've been making a lot of great improvements. Check out the [Change Log](CHANGELOG.md) for details -- particularly if you are moving to a higher significant version digit! + +- Check out [redux-components-map](https://github.com/wcjohnson/redux-components-map) for a solution to a common use case: dynamic tree structure changes while your app is running. + ## Documentation > **NB:** redux-components is a tool that interoperates with [Redux](http://redux.js.org/). This documentation presumes a solid grasp of the fundamentals found in the [Redux docs](http://redux.js.org) -- particularly the concepts of the Redux state tree, reducers, action creators, and selectors. diff --git a/src/subtree.coffee b/src/subtree.coffee index f214e19..4a58697 100644 --- a/src/subtree.coffee +++ b/src/subtree.coffee @@ -2,7 +2,7 @@ import { combineReducers } from 'redux' import invariant from 'invariant' import ReduxComponent from './ReduxComponent' import createClass from './createClass' - +import { willMountComponent, didMountComponent, willUnmountComponent } from './mountComponent' ##### SubtreeMixin attachComponent = (parentComponent, key, component) -> @@ -12,36 +12,37 @@ attachComponent = (parentComponent, key, component) -> parentComponent[key] = component childPath = parentComponent.path.concat( [ key ] ) component.__willMount(parentComponent.store, childPath, parentComponent) - # Return the reducer - component.reducer + component applyDescriptor = (parentComponent, key, descriptor) -> attachComponent(parentComponent, key, createComponent(descriptor)) export SubtreeMixin = { + getReducer: -> @subtreeReducer + componentWillMount: -> # Sanity check that our component supports subtrees if process.env.NODE_ENV isnt 'production' invariant(typeof @getSubtree is 'function', "redux-component of type #{@displayName} (mounted at location #{@path}) is using SubtreeMixin, but does not have a getSubtree() method.") - # Get the subtree structure - subtree = @getSubtree() - # Conjure child components and gather their reducers - __reducerMap = {} - for key, descriptor of subtree - __reducerMap[key] = applyDescriptor(@, key, descriptor) - # Create composite reducer for parent component - reducer = combineReducers(__reducerMap) - @getReducer = -> reducer + # Create subcomponents + @__subtree = {} + for key, descriptor of @getSubtree() + @__subtree[key] = applyDescriptor(@, key, descriptor) + + # Create reducer + reducerMap = {} + for key, component of @__subtree + reducerMap[key] = component.reducer + @subtreeReducer = combineReducers(reducerMap) - # Monkey-patch didMount to call subtree didMounts in the right order. - myDidMount = @__originalDidMount = @componentDidMount - @componentDidMount = -> - @[k]?.__didMount() for k of __reducerMap - myDidMount?.call(@) + componentDidMount: -> + didMountComponent(component) for key, component of @__subtree + undefined componentWillUnmount: -> - @componentDidMount = @__originalDidMount + willUnmountComponent(component) for key, component of @__subtree + undefined } ##### createComponent diff --git a/src/test/02-subtree.test.coffee b/src/test/02-subtree.test.coffee index 2b1470c..47eec06 100644 --- a/src/test/02-subtree.test.coffee +++ b/src/test/02-subtree.test.coffee @@ -1,7 +1,7 @@ { inspect } = require 'util' { expect, assert } = require 'chai' -{ createClass, mountRootComponent, createComponent, SubtreeMixin } = require '..' +{ createClass, mountRootComponent, createComponent, SubtreeMixin, willUnmountComponent } = require '..' { makeAStore } = require './helpers/store' describe 'subtree: ', -> @@ -20,6 +20,8 @@ describe 'subtree: ', -> console.log "Subcomponent.willMount" componentDidMount: -> console.log "Subcomponent.didMount" + componentWillUnmount: -> + console.log "Subcomponent.willUnmount" getReducer: -> (state = {}, action) -> switch action.type when @SET then action.payload or {} @@ -50,7 +52,15 @@ describe 'subtree: ', -> componentWillMount: -> console.log "RootComponent.willMount" componentDidMount: -> + assert(@foo.isMounted()) + assert(@bar.isMounted()) + assert(@deep.isMounted()) console.log "RootComponent.didMount" + componentWillUnmount: -> + assert(not @foo.isMounted()) + assert(not @bar.isMounted()) + assert(not @deep.isMounted()) + console.log "RootComponent.willUnmount" getSubtree: -> { foo: new Subcomponent() bar: Subcomponent @@ -86,3 +96,6 @@ describe 'subtree: ', -> expect(rootComponentInstance.foo.getValue()).to.equal('foo') expect(rootComponentInstance.bar.getValue()).to.equal('bar') expect(rootComponentInstance.deep.zazz.getValue()).to.equal('deep.zazz') + + it 'should unmount correctly', -> + willUnmountComponent(rootComponentInstance)