Skip to content

Commit 62b2a85

Browse files
authored
Merge pull request #2041 from reduxjs/nullable-context
2 parents 5a406a9 + 9455c49 commit 62b2a85

16 files changed

+72
-67
lines changed

docs/api/Provider.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ interface ProviderProps<A extends Action = AnyAction, S = any> {
3939
* to create a context to be used.
4040
* If this is used, you'll need to customize `connect` by supplying the same
4141
* context provided to the Provider.
42-
* Initial value doesn't matter, as it is overwritten with the internal state of Provider.
42+
* Set the initial value to null, and the hooks will error
43+
* if this is not overwritten by Provider.
4344
*/
44-
context?: Context<ReactReduxContextValue<S, A>>
45+
context?: Context<ReactReduxContextValue<S, A> | null>
4546

4647
/** Global configuration for the `useSelector` stability check */
4748
stabilityCheck?: StabilityCheck

docs/using-react-redux/accessing-store.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Redux store accessible to deeply nested connected components. As of React Redux
2727
by a single default context object instance generated by `React.createContext()`, called `ReactReduxContext`.
2828

2929
React Redux's `<Provider>` component uses `<ReactReduxContext.Provider>` to put the Redux store and the current store
30-
state into context, and `connect` uses `<ReactReduxContext.Consumer>` to read those values and handle updates.
30+
state into context, and `connect` uses `useContext(ReactReduxContext)` to read those values and handle updates.
3131

3232
## Using the `useStore` Hook
3333

@@ -87,8 +87,8 @@ This also provides a natural isolation of the stores as they live in separate co
8787

8888
```js
8989
// a naive example
90-
const ContextA = React.createContext();
91-
const ContextB = React.createContext();
90+
const ContextA = React.createContext(null);
91+
const ContextB = React.createContext(null);
9292

9393
// assuming reducerA and reducerB are proper reducer functions
9494
const storeA = createStore(reducerA);

src/components/Context.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,26 @@ const ContextKey = Symbol.for(`react-redux-context`)
1919
const gT: {
2020
[ContextKey]?: Map<
2121
typeof React.createContext,
22-
Context<ReactReduxContextValue>
22+
Context<ReactReduxContextValue | null>
2323
>
24-
} = (typeof globalThis !== "undefined" ? globalThis : /* fall back to a per-module scope (pre-8.1 behaviour) if `globalThis` is not available */ {}) as any;
24+
} = (
25+
typeof globalThis !== 'undefined'
26+
? globalThis
27+
: /* fall back to a per-module scope (pre-8.1 behaviour) if `globalThis` is not available */ {}
28+
) as any
2529

26-
function getContext(): Context<ReactReduxContextValue> {
30+
function getContext(): Context<ReactReduxContextValue | null> {
2731
if (!React.createContext) return {} as any
2832

2933
const contextMap = (gT[ContextKey] ??= new Map<
3034
typeof React.createContext,
31-
Context<ReactReduxContextValue>
35+
Context<ReactReduxContextValue | null>
3236
>())
3337
let realContext = contextMap.get(React.createContext)
3438
if (!realContext) {
35-
realContext = React.createContext<ReactReduxContextValue>(null as any)
39+
realContext = React.createContext<ReactReduxContextValue | null>(
40+
null as any
41+
)
3642
if (process.env.NODE_ENV !== 'production') {
3743
realContext.displayName = 'ReactRedux'
3844
}

src/components/Provider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ export interface ProviderProps<
2424
/**
2525
* Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used.
2626
* If this is used, you'll need to customize `connect` by supplying the same context provided to the Provider.
27-
* Initial value doesn't matter, as it is overwritten with the internal state of Provider.
27+
* Set the initial value to null, and the hooks will error
28+
* if this is not overwritten by Provider.
2829
*/
29-
context?: Context<ReactReduxContextValue<S, A>>
30+
context?: Context<ReactReduxContextValue<S, A> | null>
3031

3132
/** Global configuration for the `useSelector` stability check */
3233
stabilityCheck?: CheckFrequency

src/components/connect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ function connect<
584584
: contextValue!.store
585585

586586
const getServerState = didStoreComeFromContext
587-
? contextValue.getServerState
587+
? contextValue!.getServerState
588588
: store.getState
589589

590590
const childPropsSelector = React.useMemo(() => {

src/hooks/useDispatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function createDispatchHook<
1515
S = unknown,
1616
A extends Action<string> = UnknownAction
1717
// @ts-ignore
18-
>(context?: Context<ReactReduxContextValue<S, A>> = ReactReduxContext) {
18+
>(context?: Context<ReactReduxContextValue<S, A> | null> = ReactReduxContext) {
1919
const useStore =
2020
// @ts-ignore
2121
context === ReactReduxContext ? useDefaultStore : createStoreHook(context)

src/hooks/useReduxContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { ReactReduxContextValue } from '../components/Context'
1010
* @returns {Function} A `useReduxContext` hook bound to the specified context.
1111
*/
1212
export function createReduxContextHook(context = ReactReduxContext) {
13-
return function useReduxContext(): ReactReduxContextValue | null {
13+
return function useReduxContext(): ReactReduxContextValue {
1414
const contextValue = React.useContext(context)
1515

1616
if (process.env.NODE_ENV !== 'production' && !contextValue) {
@@ -19,7 +19,7 @@ export function createReduxContextHook(context = ReactReduxContext) {
1919
)
2020
}
2121

22-
return contextValue
22+
return contextValue!
2323
}
2424
}
2525

src/hooks/useSelector.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ const refEquality: EqualityFn<any> = (a, b) => a === b
4343
* @returns {Function} A `useSelector` hook bound to the specified context.
4444
*/
4545
export function createSelectorHook(
46-
context: React.Context<ReactReduxContextValue<any, any>> = ReactReduxContext
46+
context: React.Context<ReactReduxContextValue<
47+
any,
48+
any
49+
> | null> = ReactReduxContext
4750
): UseSelector {
4851
const useReduxContext =
4952
context === ReactReduxContext
@@ -83,7 +86,7 @@ export function createSelectorHook(
8386
getServerState,
8487
stabilityCheck: globalStabilityCheck,
8588
noopCheck: globalNoopCheck,
86-
} = useReduxContext()!
89+
} = useReduxContext()
8790

8891
const firstRun = React.useRef(true)
8992

src/hooks/useStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function createStoreHook<
1717
S = unknown,
1818
A extends BasicAction = UnknownAction
1919
// @ts-ignore
20-
>(context?: Context<ReactReduxContextValue<S, A>> = ReactReduxContext) {
20+
>(context?: Context<ReactReduxContextValue<S, A> | null> = ReactReduxContext) {
2121
const useReduxContext =
2222
// @ts-ignore
2323
context === ReactReduxContext
@@ -29,7 +29,7 @@ export function createStoreHook<
2929
Action2 extends BasicAction = A
3030
// @ts-ignore
3131
>() {
32-
const { store } = useReduxContext()!
32+
const { store } = useReduxContext()
3333
// @ts-ignore
3434
return store as Store<State, Action2>
3535
}

test/components/connect.spec.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2130,9 +2130,10 @@ describe('React', () => {
21302130
}
21312131
}
21322132

2133-
const context = React.createContext<
2134-
ReactReduxContextValue<any, AnyAction>
2135-
>(null as any)
2133+
const context = React.createContext<ReactReduxContextValue<
2134+
any,
2135+
AnyAction
2136+
> | null>(null)
21362137

21372138
let actualState
21382139

@@ -2171,9 +2172,10 @@ describe('React', () => {
21712172
}
21722173
}
21732174

2174-
const context = React.createContext<
2175-
ReactReduxContextValue<any, AnyAction>
2176-
>(null as any)
2175+
const context = React.createContext<ReactReduxContextValue<
2176+
any,
2177+
AnyAction
2178+
> | null>(null)
21772179

21782180
let actualState
21792181

@@ -2421,9 +2423,8 @@ describe('React', () => {
24212423
(state: RootStateType = 0, action: ActionType) =>
24222424
action.type === 'INC' ? state + 1 : state
24232425
)
2424-
const customContext = React.createContext<ReactReduxContextValue>(
2425-
null as any
2426-
)
2426+
const customContext =
2427+
React.createContext<ReactReduxContextValue | null>(null)
24272428

24282429
class A extends Component {
24292430
render() {

test/hooks/useDispatch.spec.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ describe('React', () => {
2727
})
2828
describe('createDispatchHook', () => {
2929
it("returns the correct store's dispatch function", () => {
30-
const nestedContext = React.createContext<ReactReduxContextValue>(
31-
null as any
32-
)
30+
const nestedContext =
31+
React.createContext<ReactReduxContextValue | null>(null)
3332
const useCustomDispatch = createDispatchHook(nestedContext)
3433
const { result } = renderHook(() => useDispatch(), {
3534
// eslint-disable-next-line react/prop-types

test/hooks/useReduxContext.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { renderHook } from '@testing-library/react-hooks'
22
import { createContext } from 'react'
3-
import { ReactReduxContextValue } from '../../src/components/Context'
3+
import type { ReactReduxContextValue } from '../../src/components/Context'
44
import {
55
createReduxContextHook,
66
useReduxContext,
@@ -23,7 +23,7 @@ describe('React', () => {
2323
})
2424
describe('createReduxContextHook', () => {
2525
it('throws if component is not wrapped in provider', () => {
26-
const customContext = createContext<ReactReduxContextValue>(null as any)
26+
const customContext = createContext<ReactReduxContextValue | null>(null)
2727
const useCustomReduxContext = createReduxContextHook(customContext)
2828
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
2929

test/hooks/useSelector.spec.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ describe('React', () => {
153153
}
154154

155155
const Parent = () => {
156-
const { subscription } = useContext(ReactReduxContext)
157-
appSubscription = subscription
156+
const contextVal = useContext(ReactReduxContext)
157+
appSubscription = contextVal && contextVal.subscription
158158
const count = useNormalSelector((s) => s.count)
159159
return count === 1 ? <Child /> : null
160160
}
@@ -179,8 +179,8 @@ describe('React', () => {
179179
let appSubscription: Subscription | null = null
180180

181181
const Parent = () => {
182-
const { subscription } = useContext(ReactReduxContext)
183-
appSubscription = subscription
182+
const contextVal = useContext(ReactReduxContext)
183+
appSubscription = contextVal && contextVal.subscription
184184
const count = useNormalSelector((s) => s.count)
185185
return count === 0 ? <Child /> : null
186186
}
@@ -944,9 +944,8 @@ describe('React', () => {
944944
})
945945

946946
it('subscribes to the correct store', () => {
947-
const nestedContext = React.createContext<ReactReduxContextValue>(
948-
null as any
949-
)
947+
const nestedContext =
948+
React.createContext<ReactReduxContextValue | null>(null)
950949
const useCustomSelector = createSelectorHook(nestedContext)
951950
let defaultCount: number | null = null
952951
let customCount: number | null = null

test/typetests/connect-mapstate-mapdispatch.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,25 @@
22

33
import * as React from 'react'
44
import * as ReactDOM from 'react-dom'
5+
import type { Dispatch, ActionCreator } from 'redux'
56
import {
67
Store,
7-
Dispatch,
88
AnyAction,
9-
ActionCreator,
109
createStore,
1110
bindActionCreators,
1211
ActionCreatorsMapObject,
1312
Reducer,
1413
} from 'redux'
14+
import type { ReactReduxContext, MapDispatchToProps } from '../../src/index'
1515
import {
1616
connect,
1717
ConnectedProps,
1818
Provider,
1919
DispatchProp,
2020
MapStateToProps,
21-
ReactReduxContext,
2221
ReactReduxContextValue,
2322
Selector,
2423
shallowEqual,
25-
MapDispatchToProps,
2624
useDispatch,
2725
useSelector,
2826
useStore,

test/typetests/connect-options-and-issues.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,17 @@
22
import * as PropTypes from 'prop-types'
33
import * as React from 'react'
44
import * as ReactDOM from 'react-dom'
5-
import {
6-
Store,
7-
Dispatch,
8-
AnyAction,
9-
ActionCreator,
10-
createStore,
11-
bindActionCreators,
12-
ActionCreatorsMapObject,
13-
Reducer,
14-
} from 'redux'
15-
import {
16-
connect,
5+
import type { Store, Dispatch, AnyAction, ActionCreator, Reducer } from 'redux'
6+
import { createStore, bindActionCreators, ActionCreatorsMapObject } from 'redux'
7+
import type {
178
Connect,
189
ConnectedProps,
19-
Provider,
2010
DispatchProp,
2111
MapStateToProps,
12+
} from '../../src/index'
13+
import {
14+
connect,
15+
Provider,
2216
ReactReduxContext,
2317
ReactReduxContextValue,
2418
Selector,

test/typetests/hooks.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22

33
import * as React from 'react'
44
import * as ReactDOM from 'react-dom'
5-
import { Store, Dispatch, configureStore, AnyAction } from '@reduxjs/toolkit'
5+
import type { Store, Dispatch, AnyAction } from '@reduxjs/toolkit'
6+
import { configureStore } from '@reduxjs/toolkit'
7+
import type {
8+
ReactReduxContextValue,
9+
Selector,
10+
TypedUseSelectorHook,
11+
} from '../../src/index'
612
import {
713
connect,
814
ConnectedProps,
915
Provider,
1016
DispatchProp,
1117
MapStateToProps,
1218
ReactReduxContext,
13-
ReactReduxContextValue,
14-
Selector,
1519
shallowEqual,
1620
MapDispatchToProps,
1721
useDispatch,
@@ -20,17 +24,15 @@ import {
2024
createDispatchHook,
2125
createSelectorHook,
2226
createStoreHook,
23-
TypedUseSelectorHook,
2427
} from '../../src/index'
2528

29+
import type { AppDispatch, RootState } from './counterApp'
2630
import {
2731
CounterState,
2832
counterSlice,
2933
increment,
3034
incrementAsync,
31-
AppDispatch,
3235
AppThunk,
33-
RootState,
3436
fetchCount,
3537
} from './counterApp'
3638

@@ -224,9 +226,10 @@ function testCreateHookFunctions() {
224226
type: 'TEST_ACTION'
225227
}
226228

227-
const Context = React.createContext<
228-
ReactReduxContextValue<RootState, RootAction>
229-
>(null as any)
229+
const Context = React.createContext<ReactReduxContextValue<
230+
RootState,
231+
RootAction
232+
> | null>(null)
230233

231234
// No context tests
232235
expectType<() => Dispatch<AnyAction>>(createDispatchHook())

0 commit comments

Comments
 (0)