Last active
April 1, 2024 11:06
-
-
Save egargan/20267f9d481e9493a9627a8448034b09 to your computer and use it in GitHub Desktop.
A handful of files to help Svelte and Redux work together smoothly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
Adds the given store to context, making it available to all components beneath this one in the | |
hierarchy. | |
@component | |
--> | |
<script lang="ts"> | |
import { setContext } from 'svelte'; | |
import type { Store } from '@reduxjs/toolkit'; | |
import { STORE_KEY } from './constants'; | |
export let store: Store; | |
setContext(STORE_KEY, store); | |
</script> | |
<slot /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { StoreEnhancerStoreCreator } from '@reduxjs/toolkit'; | |
/** | |
* Redux store enhancer, creating a store that's compatible with Svelte's store contract. | |
* | |
* See https://redux.js.org/understanding/thinking-in-redux/glossary#store-enhancer. | |
* | |
* @example | |
* ``` | |
* const todos = createSlice({ | |
* name: 'todos', | |
* initialState: { todos: [] }, | |
* reducers: { | |
* // ... | |
* }, | |
* }) | |
* | |
* const store = configureStore({ reducer: todos.reducer, enhancers: [ svelteStoreEnhancer ] }); | |
* ``` | |
*/ | |
export default function svelteStoreEnhancer( | |
createStoreApi: StoreEnhancerStoreCreator | |
): StoreEnhancerStoreCreator { | |
return function (reducer, initialState) { | |
const reduxStore = createStoreApi(reducer, initialState); | |
return { | |
...reduxStore, | |
subscribe(fn: (value: any) => void) { | |
fn(reduxStore.getState()); | |
return reduxStore.subscribe(() => { | |
fn(reduxStore.getState()); | |
}); | |
}, | |
}; | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getContext } from 'svelte'; | |
import type { Dispatch, Store } from '@reduxjs/toolkit'; | |
import { STORE_KEY } from './constants'; | |
/** | |
* Returns a dispatcher function for the contextual Redux store, used to dispatch actions. | |
* | |
* Assumes a Redux store is available in context via the `StoreProvider` component, or using the | |
* `STORE_KEY` directly. | |
*/ | |
export function useDispatch<T = unknown>(): Dispatch { | |
const store: Store<T> = getContext(STORE_KEY); | |
return store.dispatch; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getContext } from 'svelte'; | |
import { derived, type Readable } from 'svelte/store'; | |
import type { Selector } from '@reduxjs/toolkit'; | |
import { STORE_KEY } from './constants'; | |
/** | |
* Returns a store whose value is the selected value from the contextual Redux store. | |
* | |
* @param selector Selector function for retrieving a value from the store | |
* @param equalityFn Boolean function that determines if the selector value has changed, ensuring | |
* the store is only updated when the value has changed. | |
* | |
* @example | |
* ``` | |
* const thisTodo = useSelector((store) => store.todos[todoId]); | |
* ... | |
* <p>{$thisTodo.name}</p> | |
* ``` | |
*/ | |
export default function useSelector<T, S>( | |
selector: Selector<T, S>, | |
equalityFn?: (lhs: S, rhs: S) => boolean | |
): Readable<S> { | |
if (!equalityFn) { | |
equalityFn = (lhs: S, rhs: S) => lhs === rhs; | |
} | |
const store: Readable<T> = getContext(STORE_KEY); | |
let lastSelectorValue: S; | |
return derived(store, ($state: T, set) => { | |
const selectorValue: S = selector($state); | |
if (!equalityFn!(selectorValue, lastSelectorValue)) { | |
lastSelectorValue = selectorValue; | |
set(lastSelectorValue); | |
} | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { Store } from '@reduxjs/toolkit'; | |
import { getContext } from 'svelte'; | |
import { STORE_KEY } from './constants'; | |
/** | |
* Returns the Redux store that's available in context. | |
* | |
* Assumes a Redux store is available in context via the `StoreProvider` component, or using the | |
* `STORE_KEY` directly. | |
*/ | |
export default function useStore<T>(): Store<T> { | |
const store: Store<T> = getContext(STORE_KEY); | |
return store; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks very much @egargan, I think I actually discovered this myself too reading through all the store docs in frustration, but appreciate you clarifying.
I think I'd missed the subtlety between a $ label in Svelte and the $ subscription which is annoying.