This repository has been archived by the owner on Sep 10, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
RFC: Update middleware #182
Closed
Closed
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
803ed9c
Introduces an abstraction I'm calling "update middleware."
acdlite 0a27bdd
Test middleware composition
acdlite b8882f4
Re-implement withPropsOnChange using middleware
acdlite 9829e70
Re-implement withReducer using middleware
acdlite 51f48ff
xforms collection should be an array, not object
acdlite 5d03cc2
Clean-up
acdlite 8a7cc68
Remove extra initialization
acdlite 132fe2f
Squash sequential middleware-using components
acdlite fcd5898
Remove leftover line
acdlite File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,11 @@ | ||
import mapProps from './mapProps' | ||
import omit from './utils/omit' | ||
import createHelper from './createHelper' | ||
import createEagerFactory from './createEagerFactory' | ||
|
||
const flattenProp = propName => BaseComponent => { | ||
const factory = createEagerFactory(BaseComponent) | ||
return props => ( | ||
factory({ | ||
...omit(props, [propName]), | ||
...props[propName] | ||
}) | ||
) | ||
} | ||
const flattenProp = propName => | ||
mapProps(props => ({ | ||
...omit(props, [propName]), | ||
...props[propName] | ||
})) | ||
|
||
export default createHelper(flattenProp, 'flattenProp') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
src/packages/recompose/utils/__tests__/createHocFromMiddleware-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from 'react' | ||
import test from 'ava' | ||
import { mount } from 'enzyme' | ||
import createHocFromMiddleware from '../createHocFromMiddleware' | ||
import { compose } from '../../' | ||
|
||
const m1 = ({ getProps }) => next => ({ | ||
update: ({ foo, ...rest }) => { | ||
const currentProps = getProps() | ||
next.update({ | ||
...rest, | ||
currentFoo: foo, | ||
previousFoo: currentProps && currentProps.foo | ||
}) | ||
} | ||
}) | ||
|
||
const m2 = ({ getProps }) => next => ({ | ||
update: ({ bar, ...rest }) => { | ||
const currentProps = getProps() | ||
next.update({ | ||
...rest, | ||
currentBar: bar, | ||
previousBar: currentProps && currentProps.bar | ||
}) | ||
} | ||
}) | ||
|
||
const hoc1 = createHocFromMiddleware(m1) | ||
const hoc2 = createHocFromMiddleware(m2) | ||
|
||
// Does not use middleware, so will create an extra class | ||
const identityHoc = BaseComponent => | ||
class extends React.Component { | ||
render() { | ||
return <BaseComponent {...this.props} /> | ||
} | ||
} | ||
|
||
const getNumberOfComponents = wrapper => | ||
wrapper.findWhere(w => typeof w.type() !== 'string').length | ||
|
||
const assertHoc = (t, testHoc, expectedNumberOfComponents) => { | ||
const Div = testHoc('div') | ||
const wrapper = mount(<Div foo={1} bar={2} />) | ||
const div = wrapper.find('div') | ||
|
||
t.is(div.prop('currentFoo'), 1) | ||
t.is(div.prop('previousFoo'), undefined) | ||
t.is(div.prop('currentBar'), 2) | ||
t.is(div.prop('previousBar'), undefined) | ||
|
||
wrapper.setProps({ foo: 2, bar: 3 }) | ||
t.is(div.prop('currentFoo'), 2) | ||
t.is(div.prop('previousFoo'), 1) | ||
t.is(div.prop('currentBar'), 3) | ||
t.is(div.prop('previousBar'), 2) | ||
|
||
const numberOfComponents = getNumberOfComponents(wrapper) | ||
t.is(numberOfComponents, expectedNumberOfComponents) | ||
|
||
return wrapper | ||
} | ||
|
||
test('createHocFromMiddleware gives access to current props', t => { | ||
assertHoc(t, createHocFromMiddleware(m1, m2), 1) | ||
}) | ||
|
||
test('createHocFromMiddleware squashes with middleware from base component', t => { | ||
assertHoc(t, compose(hoc1, hoc2), 1) | ||
assertHoc(t, compose(hoc1, hoc2, identityHoc), 2) | ||
assertHoc(t, compose(hoc1, identityHoc, hoc2), 3) | ||
assertHoc(t, compose(identityHoc, hoc1, hoc2), 2) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { Component } from 'react' | ||
import createEagerFactory from '../createEagerFactory' | ||
|
||
const MIDDLEWARE_INFO = typeof Symbol === 'function' | ||
? Symbol() | ||
: '@@recompose/middlewareInfo' | ||
|
||
const createComponentFromMiddleware = (middlewares, BaseComponent) => { | ||
const factory = createEagerFactory(BaseComponent) | ||
class CreateComponentFromMiddleware extends Component { | ||
constructor(initialProps) { | ||
super(initialProps) | ||
this.pendingXformProps = [initialProps] | ||
this.xformProps = [] | ||
|
||
this.xforms = middlewares.reduce((xforms, middleware, i) => { | ||
const createXform = middleware({ | ||
getProps: () => this.xformProps[i] | ||
}) | ||
xforms[i] = createXform({ | ||
update: (props, cb) => { | ||
this.xformProps[i] = this.pendingXformProps[i] | ||
this.pendingXformProps[i + 1] = props | ||
if (i === middlewares.length - 1) { | ||
this.applyPropUpdate(props, cb) | ||
} else { | ||
this.xforms[i + 1].update(props, cb) | ||
} | ||
} | ||
}) | ||
return xforms | ||
}, []) | ||
} | ||
|
||
state = { childProps: null } | ||
_isMounted = false | ||
didReceivePropUpdate = false | ||
|
||
destroy() { | ||
this.xforms.forEach(xform => { | ||
if (typeof xform.destroy === 'function') { | ||
xform.destroy() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the only need the middleware returns object is |
||
} | ||
}) | ||
} | ||
|
||
applyPropUpdate(props, cb) { | ||
if (!this._isMounted) { | ||
return | ||
} | ||
this.didReceivePropUpdate = true | ||
this.setState({ | ||
childProps: props | ||
}, cb) | ||
} | ||
|
||
componentWillMount() { | ||
this._isMounted = true | ||
this.xforms[0].update(this.pendingXformProps[0]) | ||
} | ||
|
||
componentWillReceiveProps(nextProps) { | ||
this.pendingXformProps[0] = nextProps | ||
this.xforms[0].update(nextProps) | ||
} | ||
|
||
componentWillUnmount() { | ||
this._isMounted = false | ||
this.destroy() | ||
} | ||
|
||
render() { | ||
if (!this.didReceivePropUpdate) { | ||
return null | ||
} | ||
return factory(this.state.childProps) | ||
} | ||
} | ||
CreateComponentFromMiddleware[MIDDLEWARE_INFO] = { | ||
middlewares, | ||
BaseComponent | ||
} | ||
return CreateComponentFromMiddleware | ||
} | ||
|
||
const createHocFromMiddleware = (...middlewares) => BaseComponent => { | ||
if (BaseComponent[MIDDLEWARE_INFO]) { | ||
return createComponentFromMiddleware( | ||
middlewares.concat(BaseComponent[MIDDLEWARE_INFO].middlewares), | ||
BaseComponent[MIDDLEWARE_INFO].BaseComponent | ||
) | ||
} | ||
return createComponentFromMiddleware(middlewares, BaseComponent) | ||
} | ||
|
||
export default createHocFromMiddleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,33 @@ | ||
import { Component } from 'react' | ||
import pick from './utils/pick' | ||
import shallowEqual from './shallowEqual' | ||
import createHelper from './createHelper' | ||
import createEagerFactory from './createEagerFactory' | ||
import createHocFromMiddleware from './utils/createHocFromMiddleware' | ||
|
||
const withPropsOnChange = (shouldMapOrKeys, propsMapper) => BaseComponent => { | ||
const factory = createEagerFactory(BaseComponent) | ||
const withPropsOnChange = (shouldMapOrKeys, propsMapper) => { | ||
const shouldMap = typeof shouldMapOrKeys === 'function' | ||
? shouldMapOrKeys | ||
: (props, nextProps) => !shallowEqual( | ||
pick(props, shouldMapOrKeys), | ||
pick(nextProps, shouldMapOrKeys), | ||
) | ||
|
||
return class extends Component { | ||
computedProps = propsMapper(this.props); | ||
return createHocFromMiddleware(({ getProps }) => next => { | ||
let didUpdate = false | ||
let computedProps = null | ||
|
||
componentWillReceiveProps(nextProps) { | ||
if (shouldMap(this.props, nextProps)) { | ||
this.computedProps = propsMapper(nextProps) | ||
return { | ||
update: (nextProps, cb) => { | ||
if (!didUpdate || shouldMap(getProps(), nextProps)) { | ||
didUpdate = true | ||
computedProps = propsMapper(nextProps) | ||
} | ||
next.update({ | ||
...nextProps, | ||
...computedProps | ||
}, cb) | ||
} | ||
} | ||
|
||
render() { | ||
return factory({ | ||
...this.props, | ||
...this.computedProps | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
export default createHelper(withPropsOnChange, 'withPropsOnChange') |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case it's not clear why this is included in this PR (other than being the right way to define
flattenProp
, anyway), the idea is that there are some helpers that we don't want to define using middleware, because middleware necessitates using a class component, even when a functional component would suffice. What we really want is to use a functional component unless the HOC is composed with another HOC which must use a class. Put another way, a single functional component is better than a class component, but a single class component is better than a class component and a functional component.To achieve this, we will special case
mapProps
to use a functional component by default, but use middleware when being composed with other middleware-using HOCs.