- commit
- 60440e9
- parent
- fb0b016
- author
- Eric Bower
- date
- 2023-12-18 18:59:28 +0000 UTC
docs: readme
1 files changed,
+50,
-9
+50,
-9
1@@ -42,13 +42,13 @@ As I've been developing these specialized thunks, I'm starting to think of them
2 more like micro-controllers. Only thunks and endpoints have the ability to
3 update state. However, thunks are not tied to any particular view and in that
4 way are more composable. Thunks can call other thunks and you have the async
5-flow control tools from effection to faciliate coordination.
6+flow control tools from effection to facilitate coordination.
7
8 Every thunk that is created requires a unique id -- user provided string. This
9 provides us with a handful of benefits:
10
11 - User hand-labels each thunk created
12-- Better tracability (via labels)
13+- Better traceability (via labels)
14 - Easier to debug async and side-effects in general (via labels)
15 - Build abstractions off naming conventions (e.g. creating routers
16 `/users [GET]`)
17@@ -74,15 +74,19 @@ thunks.use(function* (ctx, next) {
18 });
19
20 // create a thunk
21-const log = thunks.create("log", function* (ctx, next) {
22- const resp = yield* call(fetch("https://bower.sh"));
23- const data = yield* call(resp.json());
24+const log = thunks.create<string>("log", function* (ctx, next) {
25+ const resp = yield* call(
26+ fetch("https://log-drain.com", {
27+ method: "POST",
28+ body: JSON.stringify({ message: ctx.payload }),
29+ }),
30+ );
31 console.log("before calling next middleware");
32 yield* next();
33 console.log("after all remaining middleware have run");
34 });
35
36-store.dispatch(log());
37+store.dispatch(log("sending log message"));
38 // output:
39 // before calling next middleware
40 // last mdw in the stack
41@@ -112,6 +116,42 @@ store.dispatch(fetchUsers());
42
43 # example: an immutable store that acts like a reactive, in-memory database
44
45+I love `redux`. I know it gets sniped for having too much boilerplate when
46+alternatives like `zustand` and `react-query` exist that cut through the
47+ceremony of managing state. However, `redux` was never designed to be easy to
48+use, it was designed to be scalable, debuggable, and maintainable. Yes, setting
49+up a `redux` store is work, but that is in an effort to serve its
50+maintainability.
51+
52+Having said that, the core abstraction in `redux` is a reducer. Reducers were
53+originally designed to contain isolated business logic of updating sections of
54+state (also known as state slices). They were also designed to make it easier to
55+sustain state immutability.
56+
57+Fast forward to `redux-toolkit` and we have `createSlice` which leverages
58+`immer` under-the-hood to ensure immutability. So we no longer need reducers for
59+immutability.
60+
61+Further, I argue, placing the business logic for updating state inside reducers
62+(via switch-cases) makes understanding business logic harder. Instead of having
63+a single function that updates X state slices, we have X functions (reducers)
64+that we need to piece together in our heads to understand what is being updated
65+when an action is dispatched.
66+
67+Therefore, reducers are not great containers for business logic. They are rigid
68+and require the end-developer to piece them together in their head to fully
69+understand the ramifications of dispatching an action.
70+
71+With all of this in mind, `starfx/store` takes all the good parts of `redux` and
72+removes the need for reducers entirely. We still have a single state object that
73+contains everything from data fetched from an API, UX, and a way to create
74+memoized functions (e.g. selectors). We maintain immutability (using `immer`)
75+and also have a middleware system to extend it.
76+
77+Finally, we bring the utility of creating a schema (like `zod` or a traditional
78+database) to make it plainly obvious what the state shape looks like as well as
79+reusable utilities to make it easy to update and query state.
80+
81 ```ts
82 import { configureStore, createSchema, select, slice } from "starfx/store";
83
84@@ -127,6 +167,7 @@ const { db, initialState, update } = createSchema({
85 loaders: slice.loader(),
86 });
87
88+// just a normal endpoint
89 const fetchUsers = api.get<never, User[]>(
90 "/users",
91 function* (ctx, next) {
92@@ -146,7 +187,7 @@ const fetchUsers = api.get<never, User[]>(
93 }, {});
94
95 // update the store and trigger a re-render in react
96- yield* update(db.users.add(users));
97+ yield* schema.update(db.users.add(users));
98
99 // User[]
100 const users = yield* select(db.users.selectTableAsList);
101@@ -308,10 +349,10 @@ That's it. We are just leveraging the same tiny API that we are already using in
102
103 # talk
104
105-I recently gave a talk about deliminited continuations where I also discuss this
106+I recently gave a talk about delimited continuations where I also discuss this
107 library:
108
109-[![Delminited continuations are all you need](http://img.youtube.com/vi/uRbqLGj_6mI/0.jpg)](https://youtu.be/uRbqLGj_6mI?si=Mok0J8Wp0Z-ahFrN)
110+[![Delimited continuations are all you need](http://img.youtube.com/vi/uRbqLGj_6mI/0.jpg)](https://youtu.be/uRbqLGj_6mI?si=Mok0J8Wp0Z-ahFrN)
111
112 # resources
113