Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix type of next parameter in StoreEnhancer type #3776

Merged
merged 1 commit into from
Feb 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions src/applyMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import {
StoreEnhancer,
Dispatch,
PreloadedState,
StoreEnhancerStoreCreator
} from './types/store'
import { StoreEnhancer, Dispatch, PreloadedState } from './types/store'
import { Reducer } from './types/reducers'

/**
Expand Down Expand Up @@ -60,7 +55,7 @@ export default function applyMiddleware<Ext, S = any>(
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return (createStore: StoreEnhancerStoreCreator) =>
return createStore =>
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
Expand Down
33 changes: 24 additions & 9 deletions src/createStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ import { kindOf } from './utils/kindOf'
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
export function createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext
Expand Down Expand Up @@ -68,12 +73,22 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
export function createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
export function createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
Expand Down Expand Up @@ -401,8 +416,8 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
export function legacy_createStore<
S,
A extends Action,
Ext = {},
StateExt = never
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
Expand Down Expand Up @@ -440,8 +455,8 @@ export function legacy_createStore<
export function legacy_createStore<
S,
A extends Action,
Ext = {},
StateExt = never
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
Expand All @@ -450,8 +465,8 @@ export function legacy_createStore<
export function legacy_createStore<
S,
A extends Action,
Ext = {},
StateExt = never
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export {
Store,
StoreCreator,
StoreEnhancer,
StoreEnhancerStoreCreator,
ExtendState
StoreEnhancerStoreCreator
} from './types/store'
// reducers
export {
Expand Down
43 changes: 14 additions & 29 deletions src/types/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,6 @@ import { Action, AnyAction } from './actions'
import { Reducer } from './reducers'
import '../utils/symbol-observable'

/**
* Extend the state
*
* This is used by store enhancers and store creators to extend state.
* If there is no state extension, it just returns the state, as is, otherwise
* it returns the state joined with its extension.
*
* Reference for future devs:
* https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
*/
export type ExtendState<State, Extension> = [Extension] extends [never]
? State
: State & Extension

/**
* Internal "virtual" symbol used to make the `CombinedState` type unique.
*/
Expand Down Expand Up @@ -134,11 +120,7 @@ export type Observer<T> = {
* @template A the type of actions which may be dispatched by this store.
* @template StateExt any extension to state from store enhancers
*/
export interface Store<
S = any,
A extends Action = AnyAction,
StateExt = never
> {
export interface Store<S = any, A extends Action = AnyAction, StateExt = {}> {
/**
* Dispatches an action. It is the only way to trigger a state change.
*
Expand Down Expand Up @@ -172,7 +154,7 @@ export interface Store<
*
* @returns The current state tree of your application.
*/
getState(): ExtendState<S, StateExt>
getState(): S & StateExt

/**
* Adds a change listener. It will be called any time an action is
Expand Down Expand Up @@ -217,7 +199,7 @@ export interface Store<
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
[Symbol.observable](): Observable<ExtendState<S, StateExt>>
[Symbol.observable](): Observable<S & StateExt>
}

/**
Expand All @@ -232,11 +214,11 @@ export interface Store<
* @template StateExt State extension that is mixed into the state type.
*/
export interface StoreCreator {
<S, A extends Action, Ext = {}, StateExt = never>(
<S, A extends Action, Ext extends {} = {}, StateExt extends {} = {}>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext
<S, A extends Action, Ext = {}, StateExt = never>(
<S, A extends Action, Ext extends {} = {}, StateExt extends {} = {}>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext>
Expand Down Expand Up @@ -264,13 +246,16 @@ export interface StoreCreator {
* @template Ext Store extension that is mixed into the Store type.
* @template StateExt State extension that is mixed into the state type.
*/
export type StoreEnhancer<Ext = {}, StateExt = never> = (
next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
S = any,
A extends Action = AnyAction
export type StoreEnhancer<Ext extends {} = {}, StateExt extends {} = {}> = <
NextExt extends {},
NextStateExt extends {}
>(
next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
) => StoreEnhancerStoreCreator<NextExt & Ext, NextStateExt & StateExt>
export type StoreEnhancerStoreCreator<
Ext extends {} = {},
StateExt extends {} = {}
> = <S = any, A extends Action = AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => Store<S, A, StateExt> & Ext
92 changes: 76 additions & 16 deletions test/typescript/enhancers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ function stateExtension() {
reducer: Reducer<S, A>,
preloadedState?: any
) => {
const wrappedReducer: Reducer<S & ExtraState, A> = (state, action) => {
const newState = reducer(state, action)
return {
...newState,
extraField: 'extra'
function wrapReducer(reducer: Reducer<S, A>): Reducer<S & ExtraState, A> {
return (state, action) => {
const newState = reducer(state, action)
return {
...newState,
extraField: 'extra'
}
}
}
const wrappedPreloadedState = preloadedState
Expand All @@ -77,7 +79,13 @@ function stateExtension() {
extraField: 'extra'
}
: undefined
return createStore(wrappedReducer, wrappedPreloadedState)
const store = createStore(wrapReducer(reducer), wrappedPreloadedState)
return {
...store,
replaceReducer(nextReducer: Reducer<S, A>) {
store.replaceReducer(wrapReducer(nextReducer))
}
}
}

const store = createStore(reducer, enhancer)
Expand All @@ -96,8 +104,10 @@ function extraMethods() {
createStore =>
(...args) => {
const store = createStore(...args)
store.method = () => 'foo'
return store
return {
...store,
method: () => 'foo'
}
}

const store = createStore(reducer, enhancer)
Expand All @@ -122,11 +132,13 @@ function replaceReducerExtender() {
reducer: Reducer<S, A>,
preloadedState?: any
) => {
const wrappedReducer: Reducer<S & ExtraState, A> = (state, action) => {
const newState = reducer(state, action)
return {
...newState,
extraField: 'extra'
function wrapReducer(reducer: Reducer<S, A>): Reducer<S & ExtraState, A> {
return (state, action) => {
const newState = reducer(state, action)
return {
...newState,
extraField: 'extra'
}
}
}
const wrappedPreloadedState = preloadedState
Expand All @@ -135,7 +147,14 @@ function replaceReducerExtender() {
extraField: 'extra'
}
: undefined
return createStore(wrappedReducer, wrappedPreloadedState)
const store = createStore(wrapReducer(reducer), wrappedPreloadedState)
return {
...store,
replaceReducer(nextReducer: Reducer<S, A>) {
store.replaceReducer(wrapReducer(nextReducer))
},
method: () => 'foo'
}
}

const store = createStore<
Expand Down Expand Up @@ -270,14 +289,14 @@ function finalHelmersonExample() {
<S, A extends Action<unknown>>(
reducer: Reducer<S, A>,
preloadedState?: any
): Store<S, A, ExtraState> & { persistor: Store<S, A, ExtraState> } => {
) => {
const persistedReducer = persistReducer<S, A>(persistConfig, reducer)
const store = createStore(persistedReducer, preloadedState)
const persistor = persistStore(store)

return {
...store,
replaceReducer: nextReducer => {
replaceReducer: (nextReducer: Reducer<S, A>) => {
store.replaceReducer(persistReducer(persistConfig, nextReducer))
},
persistor
Expand Down Expand Up @@ -308,3 +327,44 @@ function finalHelmersonExample() {
// @ts-expect-error
store.getState().wrongField
}

function composedEnhancers() {
Copy link
Member Author

@Methuselah96 Methuselah96 May 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new test that would fail without this fix.

interface State {
someState: string
}
const reducer: Reducer<State> = null as any

interface Ext1 {
enhancer1: string
}
interface Ext2 {
enhancer2: number
}

const enhancer1: StoreEnhancer<Ext1> =
createStore => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
return {
...store,
enhancer1: 'foo'
}
}

const enhancer2: StoreEnhancer<Ext2> =
createStore => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
return {
...store,
enhancer2: 5
}
}

const composedEnhancer: StoreEnhancer<Ext1 & Ext2> = createStore =>
enhancer2(enhancer1(createStore))

const enhancedStore = createStore(reducer, composedEnhancer)
enhancedStore.enhancer1
enhancedStore.enhancer2
// @ts-expect-error
enhancedStore.enhancer3
}
43 changes: 1 addition & 42 deletions test/typescript/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import {
Action,
StoreEnhancer,
Unsubscribe,
Observer,
ExtendState
Observer
} from '../../src'
import 'symbol-observable'

Expand All @@ -22,46 +21,6 @@ type State = {
e: BrandedString
}

/* extended state */
const noExtend: ExtendState<State, never> = {
a: 'a',
b: {
c: 'c',
d: 'd'
},
e: brandedString
}

const noExtendError: ExtendState<State, never> = {
a: 'a',
b: {
c: 'c',
d: 'd'
},
e: brandedString,
// @ts-expect-error
f: 'oops'
}

const yesExtend: ExtendState<State, { yes: 'we can' }> = {
a: 'a',
b: {
c: 'c',
d: 'd'
},
e: brandedString,
yes: 'we can'
}
// @ts-expect-error
const yesExtendError: ExtendState<State, { yes: 'we can' }> = {
a: 'a',
b: {
c: 'c',
d: 'd'
},
e: brandedString
}

interface DerivedAction extends Action {
type: 'a'
b: 'b'
Expand Down