-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
connectAdvanced.js
256 lines (221 loc) · 9.09 KB
/
connectAdvanced.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'
import React, { Component, PureComponent } from 'react'
import propTypes from 'prop-types'
import { isValidElementType } from 'react-is'
import Context from './Context'
const ReduxConsumer = Context.Consumer
export default function connectAdvanced(
/*
selectorFactory is a func that is responsible for returning the selector function used to
compute new props from state, props, and dispatch. For example:
export default connectAdvanced((dispatch, options) => (state, props) => ({
thing: state.things[props.thingId],
saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
}))(YourComponent)
Access to dispatch is provided to the factory so selectorFactories can bind actionCreators
outside of their selector as an optimization. Options passed to connectAdvanced are passed to
the selectorFactory, along with displayName and WrappedComponent, as the second argument.
Note that selectorFactory is responsible for all caching/memoization of inbound and outbound
props. Do not use connectAdvanced directly without memoizing results between calls to your
selector, otherwise the Connect component will re-render on every state or props change.
*/
selectorFactory,
// options object:
{
// the func used to compute this HOC's displayName from the wrapped component's displayName.
// probably overridden by wrapper functions such as connect()
getDisplayName = name => `ConnectAdvanced(${name})`,
// shown in error messages
// probably overridden by wrapper functions such as connect()
methodName = 'connectAdvanced',
// if defined, the name of the property passed to the wrapped element indicating the number of
// calls to render. useful for watching in react devtools for unnecessary re-renders.
renderCountProp = undefined,
// determines whether this HOC subscribes to store changes
shouldHandleStateChanges = true,
// the key of props/context to get the store [**does nothing, use consumer**]
storeKey = 'store',
// if true, the wrapped element is exposed by this HOC via the getWrappedInstance() function.
withRef = false,
// the context consumer to use
consumer = ReduxConsumer,
// additional options are passed through to the selectorFactory
...connectOptions
} = {}
) {
invariant(renderCountProp === undefined,
`renderCountProp is removed. render counting is built into the latest React dev tools profiling extension`
)
invariant(storeKey === 'store',
'storeKey has been removed and does not do anything. To use a custom redux store for a single component, ' +
'create a custom React context with React.createContext() and pass the Provider to react-redux\'s provider ' +
'and the Consumer to this component as in <Provider context={context.Provider}><' +
'ConnectedComponent consumer={context.Consumer} /></Provider>'
)
const Consumer = consumer
return function wrapWithConnect(WrappedComponent) {
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
)
invariant(!withRef || withRef === 'forwardRef',
'withRef must be set to the text "forwardRef." Reference uses React.forwardRef and you may now access ref ' +
`directly instead of using getWrappedInstance() in component ${wrappedComponentName}`
)
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component'
const displayName = getDisplayName(wrappedComponentName)
let PureWrapper
if (withRef) {
class PureWrapperRef extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.derivedProps !== this.props.derivedProps
}
render() {
let { forwardRef, derivedProps } = this.props
return <WrappedComponent {...derivedProps} ref={forwardRef} />
}
}
PureWrapperRef.propTypes = {
derivedProps: propTypes.object,
forwardRef: propTypes.oneOfType([
propTypes.func,
propTypes.object
])
}
PureWrapper = PureWrapperRef
} else {
class PureWrapperNoRef extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.derivedProps !== this.props.derivedProps
}
render() {
return <WrappedComponent {...this.props.derivedProps} />
}
}
PureWrapperNoRef.propTypes = {
derivedProps: propTypes.object,
}
PureWrapper = PureWrapperNoRef
}
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
withRef,
displayName,
wrappedComponentName,
WrappedComponent
}
const OuterBase = connectOptions.pure ? PureComponent : Component
class Connect extends OuterBase {
constructor(props) {
super(props)
invariant(withRef ? !props.props[storeKey] : !props[storeKey],
'Passing redux store in props has been removed and does not do anything. ' +
'To use a custom redux store for a single component, ' +
'create a custom React context with React.createContext() and pass the Provider to react-redux\'s provider ' +
'and the Consumer to this component\'s connect as in <Provider context={context.Provider}></Provider>' +
` and connect(mapState, mapDispatch, undefined, { consumer=context.consumer })(${wrappedComponentName})`
)
this.generatedDerivedProps = this.makeDerivedPropsGenerator()
this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
}
makeDerivedPropsGenerator() {
let lastProps
let lastState
let lastDerivedProps
let lastStore
let sourceSelector
return (state, props, store) => {
if ((connectOptions.pure && lastProps === props) && (lastState === state)) {
return lastDerivedProps
}
if (store !== lastStore) {
lastStore = store
sourceSelector = selectorFactory(store.dispatch, selectorFactoryOptions)
}
lastProps = props
lastState = state
const nextProps = sourceSelector(state, props)
if (lastDerivedProps === nextProps) {
return lastDerivedProps
}
lastDerivedProps = nextProps
return lastDerivedProps
}
}
renderWrappedComponentWithRef(value) {
invariant(value,
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
const { state, store } = value
const { forwardRef, props } = this.props
let derivedProps = this.generatedDerivedProps(state, props, store)
if (connectOptions.pure) {
return <PureWrapper derivedProps={derivedProps} forwardRef={forwardRef} />
}
return <WrappedComponent {...derivedProps} ref={forwardRef} />
}
renderWrappedComponent(value) {
invariant(value,
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
const { state, store } = value
let derivedProps = this.generatedDerivedProps(state, this.props, store)
if (connectOptions.pure) {
return <PureWrapper derivedProps={derivedProps} />
}
return <WrappedComponent {...derivedProps} />
}
render() {
if (this.props.unstable_observedBits) {
return (
<Consumer unstable_observedBits={this.props.unstable_observedBits}>
{this.renderWrappedComponent}
</Consumer>
)
}
return (
<Consumer>
{this.renderWrappedComponent}
</Consumer>
)
}
}
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
if (withRef) {
Connect.prototype.renderWrappedComponent = Connect.prototype.renderWrappedComponentWithRef
Connect.propTypes = {
props: propTypes.object,
forwardRef: propTypes.oneOfType([
propTypes.func,
propTypes.object
])
}
}
if (!withRef) {
return hoistStatics(Connect, WrappedComponent)
}
function forwardRef(props, ref) {
return <Connect props={props} forwardRef={ref} />
}
const forwarded = React.forwardRef(forwardRef)
forwarded.displayName = Connect.displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
}