Skip to content

Commit f9c07f6

Browse files
committed
feat: add support for context overwriting
1 parent a790f5f commit f9c07f6

File tree

8 files changed

+171
-75
lines changed

8 files changed

+171
-75
lines changed

packages/vue-redux/src/use-dispatch.ts renamed to packages/vue-redux/src/compositions/use-dispatch.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { useStore } from './use-store'
1+
import { createStoreComposition, useStore as useDefaultStore } from './use-store'
22
import type { Action, Dispatch, UnknownAction } from 'redux'
3+
import type {InjectionKey} from "vue";
4+
import {ContextKey, VueReduxContextValue} from "../provider/context";
35

46
/**
57
* Represents a custom composition that provides a dispatch function
@@ -48,8 +50,14 @@ export interface UseDispatch<
4850
* @returns {Function} A `useDispatch` composition bound to the specified context.
4951
*/
5052
export function createDispatchComposition<
53+
StateType = unknown,
5154
ActionType extends Action = UnknownAction,
52-
>() {
55+
>(
56+
context?: InjectionKey<VueReduxContextValue<StateType, ActionType> | null> = ContextKey,
57+
) {
58+
const useStore =
59+
context === ContextKey ? useDefaultStore : createStoreComposition(context)
60+
5361
const useDispatch = () => {
5462
const store = useStore()
5563
return store.dispatch
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {inject} from "vue";
2+
import type {InjectionKey} from "vue";
3+
import {ContextKey, VueReduxContextValue} from "../provider/context";
4+
5+
/**
6+
* Composition factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level
7+
* composition that you should usually not need to call directly.
8+
*
9+
* @param {InjectionKey<VueReduxContextValue | null>} [context=ContextKey] Context passed to your `provide`.
10+
* @returns {Function} A `useReduxContext` composition bound to the specified context.
11+
*/
12+
export function createReduxContextComposition(context = ContextKey) {
13+
return function useReduxContext(): VueReduxContextValue {
14+
const contextValue = inject(context)
15+
16+
if (process.env.NODE_ENV !== 'production' && !contextValue) {
17+
throw new Error(
18+
'could not find react-redux context value; please ensure the component is wrapped in a <Provider>',
19+
)
20+
}
21+
22+
return contextValue!
23+
}
24+
}
25+
26+
/**
27+
* A composition to access the value of the `VueReduxContext`. This is a low-level
28+
* composition that you should usually not need to call directly.
29+
*
30+
* @returns {any} the value of the `VueReduxContext`
31+
*
32+
* @example
33+
*
34+
* import { useReduxContext } from '@reduxjs/vue-redux'
35+
*
36+
* export const CounterComponent = () => {
37+
* const { store } = useReduxContext()
38+
* return <div>{store.getState()}</div>
39+
* }
40+
*/
41+
export const useReduxContext = /*#__PURE__*/ createReduxContextComposition()

packages/vue-redux/src/use-selector.ts renamed to packages/vue-redux/src/compositions/use-selector.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { inject, readonly, ref, toRaw, watch } from 'vue'
1+
import {inject, type InjectionKey, readonly, ref, toRaw, watch} from 'vue'
22
import { StoreSymbol } from './provide-store'
33
import type { StoreContext } from './provide-store'
4-
import type { DeepReadonly, Ref, UnwrapNestedRefs , UnwrapRef} from 'vue'
4+
import type { DeepReadonly, Ref, UnwrapRef} from 'vue'
55
import type { EqualityFn } from './types'
6+
import {ContextKey, VueReduxContextValue} from "../provider/context";
7+
import {
8+
createReduxContextComposition,
9+
useReduxContext as useDefaultReduxContext,
10+
} from './use-redux-context'
611

712
export interface UseSelectorOptions<Selected> {
813
equalityFn?: EqualityFn<Selected>
@@ -63,23 +68,31 @@ export interface UseSelector<StateType = unknown> {
6368
/**
6469
* Composition factory, which creates a `useSelector` composition bound to a given context.
6570
*
71+
* @param {InjectionKey<VueReduxContextValue>} [context=StoreSymbol] Injection key passed to your `inject`.
6672
* @returns {Function} A `useSelector` composition bound to the specified context.
6773
*/
68-
export function createSelectorComposition(): UseSelector {
74+
export function createSelectorComposition(
75+
context?: InjectionKey<VueReduxContextValue<any, any> | null> = ContextKey,
76+
): UseSelector {
77+
const useReduxContext =
78+
context === ContextKey
79+
? useDefaultReduxContext
80+
: createReduxContextComposition(context)
81+
6982
const useSelector = <TState, Selected>(
7083
selector: (state: TState) => Selected,
7184
equalityFnOrOptions:
7285
| EqualityFn<Selected>
7386
| UseSelectorOptions<Selected> = {},
7487
): Readonly<Ref<DeepReadonly<UnwrapRef<Selected>>>> => {
75-
const reduxContext = inject(StoreSymbol) as StoreContext
76-
7788
const { equalityFn = refEquality } =
7889
typeof equalityFnOrOptions === 'function'
7990
? { equalityFn: equalityFnOrOptions }
8091
: equalityFnOrOptions
8192

82-
const { store, subscription } = reduxContext
93+
const { store, subscription } = useReduxContext()
94+
95+
// TODO: Introduce wrappedSelector for debuggability
8396

8497
const selectedState = ref(selector(store.getState() as TState))
8598

packages/vue-redux/src/use-store.ts renamed to packages/vue-redux/src/compositions/use-store.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { inject } from 'vue'
2-
import { StoreSymbol } from './provide-store'
3-
import type {StoreContext} from './provide-store';
1+
import {inject} from 'vue'
2+
import type {InjectionKey} from 'vue'
43
import type { Action, Store } from 'redux'
4+
import {ContextKey, VueReduxContextValue} from "../provider/context";
5+
import {
6+
createReduxContextComposition,
7+
useReduxContext as useDefaultReduxContext,
8+
} from './use-redux-context'
59

610
/**
711
* Represents a type that extracts the action type from a given Redux store.
@@ -67,15 +71,22 @@ export interface UseStore<StoreType extends Store> {
6771
/**
6872
* Composition factory, which creates a `useStore` composition bound to a given context.
6973
*
74+
* @param {InjectionKey<VueReduxContextValue>} [context=StoreSymbol] Injection key passed to your `inject`.
7075
* @returns {Function} A `useStore` composition bound to the specified context.
7176
*/
7277
export function createStoreComposition<
7378
StateType = unknown,
7479
ActionType extends Action = Action,
75-
>() {
80+
>(
81+
context?: InjectionKey<VueReduxContextValue<StateType, ActionType> | null> = ContextKey,
82+
) {
83+
const useReduxContext =
84+
context === ContextKey
85+
? useDefaultReduxContext
86+
: // @ts-ignore
87+
createReduxContextComposition(context)
7688
const useStore = () => {
77-
const context = inject(StoreSymbol) as StoreContext
78-
const { store } = context
89+
const { store } = useReduxContext()
7990
return store
8091
}
8192

packages/vue-redux/src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
export * from './provide-store'
2-
export * from './use-store'
3-
export * from './use-dispatch'
4-
export * from './use-selector'
1+
export * from './provider/provider'
2+
export * from './provider/context'
3+
export * from './compositions/use-store'
4+
export * from './compositions/use-dispatch'
5+
export * from './compositions/use-selector'

packages/vue-redux/src/provide-store.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Action, Store, UnknownAction } from 'redux'
2+
import {Subscription} from '../utils/Subscription'
3+
import type { ProviderProps } from './Provider'
4+
import {InjectionKey} from "vue";
5+
6+
export interface VueReduxContextValue<
7+
SS = any,
8+
A extends Action<string> = UnknownAction,
9+
> extends Pick<ProviderProps, 'stabilityCheck' | 'identityFunctionCheck'> {
10+
store: Store<SS, A>
11+
subscription: Subscription
12+
}
13+
14+
export const ContextKey = Symbol.for(`react-redux-context`) as InjectionKey<VueReduxContextValue>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { onScopeDispose, provide } from 'vue'
2+
import type { App, InjectionKey } from 'vue'
3+
import type {Action, Store, UnknownAction} from 'redux'
4+
import {ContextKey, ProviderProps, VueReduxContextValue} from "./context";
5+
import {createSubscription, Subscription} from "../utils/Subscription";
6+
7+
export interface ProviderProps<
8+
A extends Action<string> = UnknownAction,
9+
S = unknown,
10+
> {
11+
/**
12+
* The single Redux store in your application.
13+
*/
14+
store: Store<S, A>
15+
/**
16+
* Optional context to be used internally in vue-redux. Use `Symbol() as InjectionKey<VueReduxContextValue<S, A>>` to create a context to be used.
17+
* Set the initial value to null, and the compositions will error
18+
* if this is not overwritten by `provide`.
19+
*
20+
* @see https://vuejs.org/guide/typescript/composition-api#typing-provide-inject
21+
*/
22+
context?: InjectionKey<VueReduxContextValue<S, A> | null>
23+
}
24+
25+
26+
export function getContext<
27+
A extends Action<string> = UnknownAction,
28+
S = unknown,
29+
>({ store }: Pick<ProviderProps<A, S>, "store">): VueReduxContextValue<S, A> | null {
30+
const subscription = createSubscription(store) as Subscription
31+
subscription.onStateChange = subscription.notifyNestedSubs
32+
subscription.trySubscribe()
33+
34+
return {
35+
store,
36+
subscription,
37+
}
38+
}
39+
40+
export function provideStore<
41+
A extends Action<string> = UnknownAction,
42+
S = unknown,
43+
>({store, context}: ProviderProps<A, S>) {
44+
const contextValue = getContext({store})
45+
46+
onScopeDispose(() => {
47+
contextValue.subscription.tryUnsubscribe()
48+
contextValue.subscription.onStateChange = undefined
49+
})
50+
51+
const providerKey = context || ContextKey;
52+
53+
provide(providerKey, contextValue)
54+
}
55+
56+
export function provideStoreToApp<
57+
A extends Action<string> = UnknownAction,
58+
S = unknown,
59+
>(app: App, {store, context}: ProviderProps<A, S>) {
60+
const contextValue = getContext({store})
61+
62+
const providerKey = context || ContextKey;
63+
64+
app.provide(providerKey, contextValue)
65+
}

0 commit comments

Comments
 (0)