Skip to content

Commit 939d976

Browse files
committed
Fix null pointer exception when store is given as a prop
We were trying to read contextValue.subscription, even if that value was null. Reworked logic to handle cases where the store came in as a prop.
1 parent b855e16 commit 939d976

File tree

1 file changed

+24
-13
lines changed

1 file changed

+24
-13
lines changed

src/components/connectAdvanced.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,18 +168,20 @@ export default function connectAdvanced(
168168
// Retrieve the store and ancestor subscription via context, if available
169169
const contextValue = useContext(ContextToUse)
170170

171-
172171
// The store _must_ exist as either a prop or in context
172+
const didStoreComeFromProps = Boolean(props.store)
173+
const didStoreComeFromContext =
174+
Boolean(contextValue) && Boolean(contextValue.store)
175+
173176
invariant(
174-
props.store || contextValue,
177+
didStoreComeFromProps || didStoreComeFromContext,
175178
`Could not find "store" in the context of ` +
176179
`"${displayName}". Either wrap the root component in a <Provider>, ` +
177180
`or pass a custom React context provider to <Provider> and the corresponding ` +
178181
`React context consumer to ${displayName} in connect options.`
179182
)
180183

181184
const store = props.store || contextValue.store
182-
const propsMode = Boolean(props.store)
183185

184186
const childPropsSelector = useMemo(() => {
185187
// The child props selector needs the store reference as an input.
@@ -190,9 +192,12 @@ export default function connectAdvanced(
190192
const [subscription, notifyNestedSubs] = useMemo(() => {
191193
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
192194

193-
// parentSub's source should match where store came from: props vs. context. A component
195+
// This Subscription's source should match where store came from: props vs. context. A component
194196
// connected to the store via props shouldn't use subscription from context, or vice versa.
195-
const subscription = new Subscription(store, contextValue.subscription)
197+
const subscription = new Subscription(
198+
store,
199+
didStoreComeFromProps ? null : contextValue.subscription
200+
)
196201

197202
// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
198203
// the middle of the notification loop, where `subscription` will then be null. This can
@@ -203,26 +208,32 @@ export default function connectAdvanced(
203208
)
204209

205210
return [subscription, notifyNestedSubs]
206-
}, [store, contextValue.subscription])
211+
}, [store, didStoreComeFromProps, contextValue])
207212

208-
// Determine what {store, subscription} value should be put into nested context, if necessary
213+
// Determine what {store, subscription} value should be put into nested context, if necessary,
214+
// and memoize that value to avoid unnecessary context updates.
209215
const overriddenContextValue = useMemo(() => {
216+
if (didStoreComeFromProps) {
217+
// This component is directly subscribed to a store from props.
218+
// We don't want descendants reading from this store - pass down whatever
219+
// the existing context value is from the nearest connected ancestor.
220+
return contextValue
221+
}
210222

211223
// Otherwise, put this component's subscription instance into context, so that
212224
// connected descendants won't update until after this component is done
213225
return {
214226
...contextValue,
215227
subscription
216228
}
217-
}, [contextValue, subscription])
229+
}, [didStoreComeFromProps, contextValue, subscription])
218230

219231
// We need to force this wrapper component to re-render whenever a Redux store update
220232
// causes a change to the calculated child component props (or we caught an error in mapState)
221-
const [[previousStateUpdateResult], forceComponentUpdateDispatch] = useReducer(
222-
storeStateUpdatesReducer,
223-
EMPTY_ARRAY,
224-
initStateUpdates
225-
)
233+
const [
234+
[previousStateUpdateResult],
235+
forceComponentUpdateDispatch
236+
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
226237

227238
// Propagate any mapState/mapDispatch errors upwards
228239
if (previousStateUpdateResult && previousStateUpdateResult.error) {

0 commit comments

Comments
 (0)