From 15670e4988de231ef8f2e24059e7b1bbc5d2fd02 Mon Sep 17 00:00:00 2001 From: "William C. Johnson" Date: Sun, 12 Feb 2017 19:31:54 -0400 Subject: [PATCH] Docs updates --- CHANGELOG.md | 23 +++++++++++ docs/API/SubtreeMixin.md | 13 ++++-- docs/API/mountComponent.md | 70 +++++++++++++++++++++------------ docs/Advanced/DynamicReducer.md | 2 +- package.json | 6 +-- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b9e93..6231337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +# 0.3 + +### **BREAKING CHANGE:** New component mounting API + +In light of the architectural changes in 0.2, and because the old API was quite confusing, we've designed a new API for mounting components. The `mounter` function has been eliminated and replaced with an imperative API for mounting and unmounting. + +For those managing their entire state tree with `redux-components`, the transition should be easy: replace your single `mountComponent` statement with the new `mountRootComponent`, which has the same argument signature. + +Those with more complex state tree designs will want to [read the docs](https://wcjohnson.gitbooks.io/redux-components/content/docs/API/mountComponent.html). + +### ReduxComponentClasses can now be used as mixins. + +A `ReduxComponentClass` (the object returned by `createClass`) can now be used as a mixin on any other class. This can be used to simulate class inheritance in many use cases. + +### actionDispatchers now support falsy return values + +In 0.2, `actionDispatchers` were added to directly dispatch actions to a mounted component's store. They were required to return a valid Redux action. Now they may return a falsy value, in which case no action will be dispatched. + +### Minor changes and fixes + +- Redux is now listed as a dependency rather than a peerDependency. +- The internal `__reducerMap` variable on `SubtreeMixin` instances has been removed. + # 0.2 This is a major release that brings some changes that my team is excited about. diff --git a/docs/API/SubtreeMixin.md b/docs/API/SubtreeMixin.md index 6329284..79ba1b0 100644 --- a/docs/API/SubtreeMixin.md +++ b/docs/API/SubtreeMixin.md @@ -20,8 +20,8 @@ When the parent component is mounted, child components are instantiated using `c In vanilla Redux, a root reducer is often the result of a `combineReducers` over several reducers handling separate branches of app state. In redux-components, the corresponding pattern is a component using `SubtreeMixin` to combine several subcomponents: ```coffeescript -createClass = require 'redux-components/createClass' -SubtreeMixin = require 'redux-components/SubtreeMixin' +{ createStore } = require 'redux' +{ createClass, SubtreeMixin, mountRootComponent } = require 'redux-components' Foo = require 'MyComponents/Foo', ... RootComponent = createClass { @@ -35,6 +35,13 @@ RootComponent = createClass { ... } } + +rootComponent = new RootComponent + +# Create store with empty reducer +store = createStore( (x) -> x ) +# Generate the reducer from the component tree and attach it. +mountRootComponent(store, rootComponent) ``` ## Details @@ -43,4 +50,4 @@ When a component with a `SubtreeMixin` enters `componentWillMount`, it calls `ge ## Notes -> `SubtreeMixin` is an optimized implementation for the most common use case: nodes with a static shape. This is when the keys and subcomponent classes returned by getSubtree do not depend on the Redux state or change throughout the app lifecycle. For subtrees that are stateful or dynamic, see `DynamicSubtreeMixin`. +> `SubtreeMixin` is an optimized implementation for the most common use case: nodes with a static shape. This is when the keys and subcomponent classes returned by getSubtree do not depend on the Redux state or change throughout the app lifecycle. For subtrees that are stateful or dynamic, see the `redux-components-map` project. diff --git a/docs/API/mountComponent.md b/docs/API/mountComponent.md index 547a0f1..8c00d1b 100644 --- a/docs/API/mountComponent.md +++ b/docs/API/mountComponent.md @@ -1,34 +1,29 @@ # mountComponent -```coffeescript -{ mountComponent } = require 'redux-components' -mountComponent: ( - store = { getState, dispatch, subscribe, replaceReducer }, - componentInstance = instanceof ReduxComponent, - path = [string|number, ...], - mounter = (store, componentInstance) -> -) -> -``` -Mounts a component instance to the redux state tree. `store` is a reference to the Redux store. The `componentInstance` is the component instance to be attached to the tree. The `path` is an array path from the root node of the state tree to the node at which this component will be mounted. - -The `mounter` is a function that will be called as the component is mounting, and additionally whenever the subtree managed by this component requests a change in reducer. It should have the effect of replacing the store's root reducer via `store.replaceReducer()` as appropriate. ## State tree design -### Root managed by redux-components; connected subtrees +Broadly speaking, there are two ways to manage your Redux state tree using redux-components. You can let redux-components manage the root of your tree (as well as all subnodes) or your state tree can be managed manually, with redux-components being mounted at one or more subnodes of the state tree. + +We discuss both of these cases. -In this case, a redux-component instance will be mounted at the root of the state tree, and hence will manage the root reducer. You will only ever call `mountComponent` once, to connect this root component. Trees of subcomponents descending from the root will be managed by `SubtreeMixin`. +### redux-component at the root -We expect this to be the typical (or if not typical, then certainly simplest) case and so we provide a default implementation of `mounter` for this scenario: +The easiest way to use redux-components is to let it manage the root node of your state tree. In this case, a redux-component instance will be mounted at the root of the state tree, and hence will manage the root reducer. To do this, use `mountRootComponent`. ```coffeescript -mounter = (store, componentInstance) -> - store.replaceReducer(componentInstance.reducer) +{ mountRootComponent } = require 'redux-components' +mountRootComponent: ( + store = { getState, dispatch, subscribe, replaceReducer }, + componentInstance = instanceof ReduxComponent +) -> ``` +`mountRootComponent` attaches a ReduxComponent to the root of the state tree, allowing it to manage the entire tree. `store` is a reference to the Redux store. The `componentInstance` is the component instance to be attached to the tree. + +Internally, this function uses `store.replaceReducer()` to attach the reducer of the `componentInstance` to the Redux store, allowing it to manage the store's whole state. If this is your use case, you will initialize your store like this: ```coffeescript -mountComponent = require 'redux-components/mountComponent' -createClass = require 'redux-components/createClass' +{ mountRootComponent, createClass } = require 'redux-components' { createStore } = require 'redux' RootComponentClass = createClass { @@ -39,17 +34,40 @@ rootComponent = new RootComponentClass # Create store with empty reducer store = createStore( (x) -> x ) # Generate the reducer from the component tree and attach it. -mountComponent(store, rootComponent) +mountRootComponent(store, rootComponent) ``` > * When using this approach, you can still attach reducers not managed by redux-components to nodes of your state tree beneath the root. Use `SubtreeMixin`. -> * If you need to use a higher-order reducer at the root of your state tree, you must provide a `mounter` that wraps `componentInstance.reducer` in your higher-order reducer before attaching it to the store. -> * Don't disconnect subtrees of redux-components! Every redux-component should have a parent that is a redux-component, except the unique root component at the top of the tree. If this is not so, you are in the (more difficult) case described below. -### Unmanaged root and/or disconnected subtrees +### Manual mounting + +If redux-components does not manage your root reducer, then you will need to mount and unmount components manually using the following API: + +```coffeescript +{ willMountComponent, didMountComponent, willUnmountComponent } = require 'redux-components' +willMountComponent: ( + store = { getState, dispatch, subscribe, replaceReducer }, + componentInstance = instanceof ReduxComponent + path = [string|number, ...] +) -> reducer + +didMountComponent: ( + componentInstance = instanceof ReduxComponent +) -> + +willUnmountComponent: ( + componentInstance = instanceof ReduxComponent +) -> +``` + +The manual mounting process works as follows: first you call `willMountComponent(store, componentInstance, path)`, providing the Redux `store`, the `componentInstance` you wish to mount, as well as the `path` from the root node of the store to the spot where the component will be mounted. The `path` is provided in the array form that would be used by `lodash.get()`. + +`willMountComponent` will return a reducer, which you must then attach to your state tree at the given `path` using something like `store.replaceReducer()`. Once the reducer is in place, you must call `didMountComponent(componentInstance)` on the component in order to honor the redux-components lifecycle contract. + +> * Do not manually mount components beneath redux-components that manage children, such as `SubtreeMixin` nodes or `redux-components-map`. This will bypass the internal logic of those components and almost certainly break your reducer tree. Use the correct APIs of the parent component to add child nodes beneath these components. -We strongly recommend allowing redux-components to manage your root reducer if possible. It makes everything work better. But maybe this is impossible for your use case. +### Manual unmounting -If so, you can implement a design where you will manage your own state tree from the root, with one or more subtrees managed by redux-components. You will call `mountComponent` for each such subtree. For each component you mount, you must provide your own implementation of `mounter` that will rebuild and reattach the root reducer, taking into account the fact that the `mountedInstance`'s reducer may have changed. +If you want to unmount a manually-mounted component, you should call `willUnmountComponent(componentInstance)` on it first. This will invoke the `willUnmount` method as required by redux-components' lifecycle contract, as well as replacing the reducer for the component with the identity. -> If you have multiple disconnected subtrees managed by redux-components, the `mounter` for each subtree must be aware of the others and reattach their reducers correctly! +You must then manually patch up the state tree. (e.g. by calling `replaceReducer()` to remove the manually-mounted reducer) Such is the nature of manual mounting, and why we recommend letting redux-components manage your root nodes. diff --git a/docs/Advanced/DynamicReducer.md b/docs/Advanced/DynamicReducer.md index ecc1ef2..949f5a6 100644 --- a/docs/Advanced/DynamicReducer.md +++ b/docs/Advanced/DynamicReducer.md @@ -12,7 +12,7 @@ An example is the `ComponentMap` class implemented in [redux-component-map](http In order to implement this behavior, it is necessary to define a new reducer function each time the tree of components changes, because the reducer function is a composition of other functions and the composition depends on the shape of the tree. This is the typical use case for which dynamic reducers were developed. -Dynamic reducers are dangerous in general. Most of the time someone needs a dynamic reducer, it is basically to implement `Map` or something like it. If that's you, consider redux-component-map before writing your own. +Dynamic reducers are dangerous in general. Most of the time someone needs a dynamic reducer, it is basically to implement `Map` or something like it. If that's you, consider `redux-components-map` before writing your own. ## How do I create a component with a dynamic reducer? diff --git a/package.json b/package.json index 420b281..a07c1a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-components", - "version": "0.2.3", + "version": "0.3.0", "description": "A component model for Redux state trees based on the React.js component model.", "keywords": [ "redux", @@ -51,9 +51,7 @@ }, "dependencies": { "invariant": "^2.2.2", - "symbol-observable": "^1.0.4" - }, - "peerDependencies": { + "symbol-observable": "^1.0.4", "redux": "^3.6.0" } }