Skip to content

Instantly share code, notes, and snippets.

@egargan
Last active April 1, 2024 11:06
Show Gist options
  • Save egargan/20267f9d481e9493a9627a8448034b09 to your computer and use it in GitHub Desktop.
Save egargan/20267f9d481e9493a9627a8448034b09 to your computer and use it in GitHub Desktop.
A handful of files to help Svelte and Redux work together smoothly
<!--
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 />
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());
});
},
};
};
}
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;
}
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);
}
});
}
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;
}
@IPWright83
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment