diff --git a/docs/api/Provider.md b/docs/api/Provider.md index 1ee84d727..74aa5578a 100644 --- a/docs/api/Provider.md +++ b/docs/api/Provider.md @@ -39,9 +39,10 @@ interface ProviderProps { * to create a context to be used. * If this is used, you'll need to customize `connect` by supplying the same * context provided to the Provider. - * Initial value doesn't matter, as it is overwritten with the internal state of Provider. + * Set the initial value to null, and the hooks will error + * if this is not overwritten by Provider. */ - context?: Context> + context?: Context | null> /** Global configuration for the `useSelector` stability check */ stabilityCheck?: StabilityCheck diff --git a/docs/using-react-redux/accessing-store.md b/docs/using-react-redux/accessing-store.md index 59075610f..d9d4c4061 100644 --- a/docs/using-react-redux/accessing-store.md +++ b/docs/using-react-redux/accessing-store.md @@ -27,7 +27,7 @@ Redux store accessible to deeply nested connected components. As of React Redux by a single default context object instance generated by `React.createContext()`, called `ReactReduxContext`. React Redux's `` component uses `` to put the Redux store and the current store -state into context, and `connect` uses `` to read those values and handle updates. +state into context, and `connect` uses `useContext(ReactReduxContext)` to read those values and handle updates. ## Using the `useStore` Hook @@ -87,8 +87,8 @@ This also provides a natural isolation of the stores as they live in separate co ```js // a naive example -const ContextA = React.createContext(); -const ContextB = React.createContext(); +const ContextA = React.createContext(null); +const ContextB = React.createContext(null); // assuming reducerA and reducerB are proper reducer functions const storeA = createStore(reducerA); diff --git a/src/components/Context.ts b/src/components/Context.ts index d3a2b8edf..993d6613a 100644 --- a/src/components/Context.ts +++ b/src/components/Context.ts @@ -19,20 +19,26 @@ const ContextKey = Symbol.for(`react-redux-context`) const gT: { [ContextKey]?: Map< typeof React.createContext, - Context + Context > -} = (typeof globalThis !== "undefined" ? globalThis : /* fall back to a per-module scope (pre-8.1 behaviour) if `globalThis` is not available */ {}) as any; +} = ( + typeof globalThis !== 'undefined' + ? globalThis + : /* fall back to a per-module scope (pre-8.1 behaviour) if `globalThis` is not available */ {} +) as any -function getContext(): Context { +function getContext(): Context { if (!React.createContext) return {} as any const contextMap = (gT[ContextKey] ??= new Map< typeof React.createContext, - Context + Context >()) let realContext = contextMap.get(React.createContext) if (!realContext) { - realContext = React.createContext(null as any) + realContext = React.createContext( + null as any + ) if (process.env.NODE_ENV !== 'production') { realContext.displayName = 'ReactRedux' } diff --git a/src/components/Provider.tsx b/src/components/Provider.tsx index 85159bfdd..7ddfc7e89 100644 --- a/src/components/Provider.tsx +++ b/src/components/Provider.tsx @@ -24,9 +24,10 @@ export interface ProviderProps< /** * Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used. * If this is used, you'll need to customize `connect` by supplying the same context provided to the Provider. - * Initial value doesn't matter, as it is overwritten with the internal state of Provider. + * Set the initial value to null, and the hooks will error + * if this is not overwritten by Provider. */ - context?: Context> + context?: Context | null> /** Global configuration for the `useSelector` stability check */ stabilityCheck?: CheckFrequency diff --git a/src/components/connect.tsx b/src/components/connect.tsx index d76025eeb..9f93e636d 100644 --- a/src/components/connect.tsx +++ b/src/components/connect.tsx @@ -584,7 +584,7 @@ function connect< : contextValue!.store const getServerState = didStoreComeFromContext - ? contextValue.getServerState + ? contextValue!.getServerState : store.getState const childPropsSelector = React.useMemo(() => { diff --git a/src/hooks/useDispatch.ts b/src/hooks/useDispatch.ts index d3f0f2439..c5520dc43 100644 --- a/src/hooks/useDispatch.ts +++ b/src/hooks/useDispatch.ts @@ -15,7 +15,7 @@ export function createDispatchHook< S = unknown, A extends Action = UnknownAction // @ts-ignore ->(context?: Context> = ReactReduxContext) { +>(context?: Context | null> = ReactReduxContext) { const useStore = // @ts-ignore context === ReactReduxContext ? useDefaultStore : createStoreHook(context) diff --git a/src/hooks/useReduxContext.ts b/src/hooks/useReduxContext.ts index 44fa63671..fdae9b5b7 100644 --- a/src/hooks/useReduxContext.ts +++ b/src/hooks/useReduxContext.ts @@ -10,7 +10,7 @@ import type { ReactReduxContextValue } from '../components/Context' * @returns {Function} A `useReduxContext` hook bound to the specified context. */ export function createReduxContextHook(context = ReactReduxContext) { - return function useReduxContext(): ReactReduxContextValue | null { + return function useReduxContext(): ReactReduxContextValue { const contextValue = React.useContext(context) if (process.env.NODE_ENV !== 'production' && !contextValue) { @@ -19,7 +19,7 @@ export function createReduxContextHook(context = ReactReduxContext) { ) } - return contextValue + return contextValue! } } diff --git a/src/hooks/useSelector.ts b/src/hooks/useSelector.ts index 3aa6ceb31..0d16367ec 100644 --- a/src/hooks/useSelector.ts +++ b/src/hooks/useSelector.ts @@ -43,7 +43,10 @@ const refEquality: EqualityFn = (a, b) => a === b * @returns {Function} A `useSelector` hook bound to the specified context. */ export function createSelectorHook( - context: React.Context> = ReactReduxContext + context: React.Context | null> = ReactReduxContext ): UseSelector { const useReduxContext = context === ReactReduxContext @@ -83,7 +86,7 @@ export function createSelectorHook( getServerState, stabilityCheck: globalStabilityCheck, noopCheck: globalNoopCheck, - } = useReduxContext()! + } = useReduxContext() const firstRun = React.useRef(true) diff --git a/src/hooks/useStore.ts b/src/hooks/useStore.ts index 2655fa634..cc529d315 100644 --- a/src/hooks/useStore.ts +++ b/src/hooks/useStore.ts @@ -17,7 +17,7 @@ export function createStoreHook< S = unknown, A extends BasicAction = UnknownAction // @ts-ignore ->(context?: Context> = ReactReduxContext) { +>(context?: Context | null> = ReactReduxContext) { const useReduxContext = // @ts-ignore context === ReactReduxContext @@ -29,7 +29,7 @@ export function createStoreHook< Action2 extends BasicAction = A // @ts-ignore >() { - const { store } = useReduxContext()! + const { store } = useReduxContext() // @ts-ignore return store as Store } diff --git a/test/components/connect.spec.tsx b/test/components/connect.spec.tsx index 28c2c8a13..0e051da07 100644 --- a/test/components/connect.spec.tsx +++ b/test/components/connect.spec.tsx @@ -2130,9 +2130,10 @@ describe('React', () => { } } - const context = React.createContext< - ReactReduxContextValue - >(null as any) + const context = React.createContext | null>(null) let actualState @@ -2171,9 +2172,10 @@ describe('React', () => { } } - const context = React.createContext< - ReactReduxContextValue - >(null as any) + const context = React.createContext | null>(null) let actualState @@ -2421,9 +2423,8 @@ describe('React', () => { (state: RootStateType = 0, action: ActionType) => action.type === 'INC' ? state + 1 : state ) - const customContext = React.createContext( - null as any - ) + const customContext = + React.createContext(null) class A extends Component { render() { diff --git a/test/hooks/useDispatch.spec.tsx b/test/hooks/useDispatch.spec.tsx index d91343515..5fecddee8 100644 --- a/test/hooks/useDispatch.spec.tsx +++ b/test/hooks/useDispatch.spec.tsx @@ -27,9 +27,8 @@ describe('React', () => { }) describe('createDispatchHook', () => { it("returns the correct store's dispatch function", () => { - const nestedContext = React.createContext( - null as any - ) + const nestedContext = + React.createContext(null) const useCustomDispatch = createDispatchHook(nestedContext) const { result } = renderHook(() => useDispatch(), { // eslint-disable-next-line react/prop-types diff --git a/test/hooks/useReduxContext.spec.tsx b/test/hooks/useReduxContext.spec.tsx index 0a2278aea..f36c4ed1e 100644 --- a/test/hooks/useReduxContext.spec.tsx +++ b/test/hooks/useReduxContext.spec.tsx @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react-hooks' import { createContext } from 'react' -import { ReactReduxContextValue } from '../../src/components/Context' +import type { ReactReduxContextValue } from '../../src/components/Context' import { createReduxContextHook, useReduxContext, @@ -23,7 +23,7 @@ describe('React', () => { }) describe('createReduxContextHook', () => { it('throws if component is not wrapped in provider', () => { - const customContext = createContext(null as any) + const customContext = createContext(null) const useCustomReduxContext = createReduxContextHook(customContext) const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) diff --git a/test/hooks/useSelector.spec.tsx b/test/hooks/useSelector.spec.tsx index ae38a10bb..81839b71e 100644 --- a/test/hooks/useSelector.spec.tsx +++ b/test/hooks/useSelector.spec.tsx @@ -153,8 +153,8 @@ describe('React', () => { } const Parent = () => { - const { subscription } = useContext(ReactReduxContext) - appSubscription = subscription + const contextVal = useContext(ReactReduxContext) + appSubscription = contextVal && contextVal.subscription const count = useNormalSelector((s) => s.count) return count === 1 ? : null } @@ -179,8 +179,8 @@ describe('React', () => { let appSubscription: Subscription | null = null const Parent = () => { - const { subscription } = useContext(ReactReduxContext) - appSubscription = subscription + const contextVal = useContext(ReactReduxContext) + appSubscription = contextVal && contextVal.subscription const count = useNormalSelector((s) => s.count) return count === 0 ? : null } @@ -944,9 +944,8 @@ describe('React', () => { }) it('subscribes to the correct store', () => { - const nestedContext = React.createContext( - null as any - ) + const nestedContext = + React.createContext(null) const useCustomSelector = createSelectorHook(nestedContext) let defaultCount: number | null = null let customCount: number | null = null diff --git a/test/typetests/connect-mapstate-mapdispatch.tsx b/test/typetests/connect-mapstate-mapdispatch.tsx index 549119d0e..d2bb22c07 100644 --- a/test/typetests/connect-mapstate-mapdispatch.tsx +++ b/test/typetests/connect-mapstate-mapdispatch.tsx @@ -2,27 +2,25 @@ import * as React from 'react' import * as ReactDOM from 'react-dom' +import type { Dispatch, ActionCreator } from 'redux' import { Store, - Dispatch, AnyAction, - ActionCreator, createStore, bindActionCreators, ActionCreatorsMapObject, Reducer, } from 'redux' +import type { ReactReduxContext, MapDispatchToProps } from '../../src/index' import { connect, ConnectedProps, Provider, DispatchProp, MapStateToProps, - ReactReduxContext, ReactReduxContextValue, Selector, shallowEqual, - MapDispatchToProps, useDispatch, useSelector, useStore, diff --git a/test/typetests/connect-options-and-issues.tsx b/test/typetests/connect-options-and-issues.tsx index 9e1260e8f..92d69116e 100644 --- a/test/typetests/connect-options-and-issues.tsx +++ b/test/typetests/connect-options-and-issues.tsx @@ -2,23 +2,17 @@ import * as PropTypes from 'prop-types' import * as React from 'react' import * as ReactDOM from 'react-dom' -import { - Store, - Dispatch, - AnyAction, - ActionCreator, - createStore, - bindActionCreators, - ActionCreatorsMapObject, - Reducer, -} from 'redux' -import { - connect, +import type { Store, Dispatch, AnyAction, ActionCreator, Reducer } from 'redux' +import { createStore, bindActionCreators, ActionCreatorsMapObject } from 'redux' +import type { Connect, ConnectedProps, - Provider, DispatchProp, MapStateToProps, +} from '../../src/index' +import { + connect, + Provider, ReactReduxContext, ReactReduxContextValue, Selector, diff --git a/test/typetests/hooks.tsx b/test/typetests/hooks.tsx index d0092856d..c35cc0be2 100644 --- a/test/typetests/hooks.tsx +++ b/test/typetests/hooks.tsx @@ -2,7 +2,13 @@ import * as React from 'react' import * as ReactDOM from 'react-dom' -import { Store, Dispatch, configureStore, AnyAction } from '@reduxjs/toolkit' +import type { Store, Dispatch, AnyAction } from '@reduxjs/toolkit' +import { configureStore } from '@reduxjs/toolkit' +import type { + ReactReduxContextValue, + Selector, + TypedUseSelectorHook, +} from '../../src/index' import { connect, ConnectedProps, @@ -10,8 +16,6 @@ import { DispatchProp, MapStateToProps, ReactReduxContext, - ReactReduxContextValue, - Selector, shallowEqual, MapDispatchToProps, useDispatch, @@ -20,17 +24,15 @@ import { createDispatchHook, createSelectorHook, createStoreHook, - TypedUseSelectorHook, } from '../../src/index' +import type { AppDispatch, RootState } from './counterApp' import { CounterState, counterSlice, increment, incrementAsync, - AppDispatch, AppThunk, - RootState, fetchCount, } from './counterApp' @@ -224,9 +226,10 @@ function testCreateHookFunctions() { type: 'TEST_ACTION' } - const Context = React.createContext< - ReactReduxContextValue - >(null as any) + const Context = React.createContext | null>(null) // No context tests expectType<() => Dispatch>(createDispatchHook())