Feedback from my spy in the React community: 'You really, really, really need some sort of "useReducer."'.
useReducer is documented here, and basically allows you to do this:
function reducer(count, action) {
switch (action.type) {
case 'increment':
return count + 1;
case 'decrement':
return count - 1;
default:
throw new Error();
}
}
const [count, dispatch] = reducible(0, reducer);
count is a store that you update by calling dispatch({ type: 'increment' }) and so on.
Personally I'm not entirely convinced that stringly-typed Redux-style dispatching is superior to creating custom stores like this, but perhaps there's some benefit I've overlooked. It would be a simple enough thing to add — the implementation is just this:
function reducible(state, reducer) {
const { update, subscribe } = writable(state);
function dispatch(action) {
update(state => reducer(state, action));
}
return [{ subscribe }, dispatch];
}
(alternative API — return { subscribe, dispatch })
I personally wouldn't mind to have something like this but it sounds/looks so react(ish). IMO people should be encouraged to split complex state into smaller and easier to eye pieces or not having this complexity at all.
Some folks like to have a defined set of actions that can effect state.
I think having a little section in some documentation with an embedded REPL example showing that reducible function used should be good enough.
If folks want that pattern, they can copy/paste reducible into their codebase.
I kind of like the creating custom stores approach you created. Seems easy to share that store with other components and you can attach methods to update the values within the store. Are there any performance issues with that?
I think Svelte should stay svelte and not encourage to handle the more complex things with built-in functionalities. Making such things available directly from the framework as React does (they only added it because it's the go-to store there) shouldn't be the favored way. But it would be cool to have it as an external lib if you're in a more complex situation.
I’d like to make a svelte knowledge graph editor with a reducer to organize immutable functional state updates on nested/circular objects. Ramda / Immer / Gremlin / React offer great inspiration for this.
IMO a writeable store in svelte with a handmade reducer and immutable option can already do what useReducer does. Just export a state object and a way to dispatch update actions. It would be cool to figure out an elegant way to walk paths in object graphs and avoid circular reference bugs if we nest stuff
What about supporting a couple of the most significant state management models in Svelte's APIs? Why not support both mutable and immutable models?
Where the APIs support mutability the language is distinctly OOP. Where the APIs support immutability the language is distinctly FP, and where both overlap the language is neutral and commonplace.
An example of a model I am interested in seeing as commonplace, regardless of scale, is presented by Martin Kleppmann in this 2015 talk about turning the database inside-out:
https://www.youtube.com/watch?v=fU9hR3kiOK0
...also reproduced in written form:
https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/
I mean, imagine not requiring any cache mechanisms at all in your app... no cache, just a store, a derived store or derived stores, subscribers, and replicating the write-events-to-log store in a Service Worker for data persistence? The read-store could be a graph derived from the log-store that represents the totality of the app state. In essence the materialised view that Martin talks about.
What Martin talks about seems to me to be the most Svelte'ish, state management model of all the options. No gnarly cache management. Easy Offline/Online without breaking a sweat. Simple to use. Streams based. Elegantly extendable in Sapper, maybe.
I think because Svelte is a compiler that supports reactivity out of the box it needs a very strong foundation for state management that can easily scale from simple to complex/high load. Redux isn't that store, Mobx isn't that store, Apollo isn't that store. They aren't made to leverage Svelte's unique features.
Maybe Svelte's Store technology could be titled "Scaling from Simple to Data-Intensive Applications Without Breaking a Sweat".
I really don't like using switches, can you pass in an object that has methods that take in the state and optional payload. then when you call it, you just call the key and if need be a payload as well.
const clicker={
increase(state,payload){
state.count++
return state;
},
decrease(state,payload){
state.count--;
return state;
}
}
const [dispatch,store]=createReducer(clicker);
dispatch("increase")
in typescript you can type check against the key and it would need to be a keyof what ever object passed in.
the state doesn't have to be the real state, it could be a copy.
then just update the writable store and anyone subscribed would get the update
you can even pass in the store to use when creating the dispatcher so you could have other dispatchers that handle different parts of the store
or perform different tasks
I wonder is there a redux-dev-tools alike to inspect/debug svelte store
Redux Dev Tools can integrate with svelte, simple example:
function redux(init, reducer) {
const devTools =
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__.connect();
const { update, subscribe } = writable(init);
function dispatch(action) {
update(state => {
devTools.send(action, state);
return reducer(state, action);
});
}
return {
subscribe,
dispatch
};
}
I needed a redux style pattern to manage large global store/state. Thank you for the snippet @Rich-Harris . It was helpful. I modified it a little bit. Placing it here, so someone else might find it valuable later.
import { get, writable } from "svelte/store";
export function createStore(reducer, initialState) {
const LOCAL_STORAGE_KEY = "store";
if (!initialState)
initialState = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || {};
const _store = writable(initialState);
const { update, subscribe } = _store;
function getState() {
return get(_store);
}
function dispatchSync(action) {
update((state) => {
const newState = reducer(state, action);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newState));
return newState;
});
}
function dispatch(action) {
if (typeof action === "function") action(dispatchSync, getState);
else dispatchSync(action);
}
return { subscribe, dispatch };
}
Remember to set immutable:true in rollup.config.js.
svelte({
// enable run-time checks when not in production
dev: !production,
immutable: true,
}),
Most helpful comment
I think Svelte should stay svelte and not encourage to handle the more complex things with built-in functionalities. Making such things available directly from the framework as React does (they only added it because it's the go-to store there) shouldn't be the favored way. But it would be cool to have it as an external lib if you're in a more complex situation.