Skip to content

Commit 5ab8256

Browse files
committed
Showcase & blog post code for reducer usage
1 parent 44b04b4 commit 5ab8256

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useCallback, useState } from "react";
22
import { RichDateTimeDisplay } from "./time/RichDateTimeDisplay";
3+
import { FlipFlop } from "./FlipFlip";
34

45
function incrementNum(a: number): number {
56
return a + 1;
@@ -48,6 +49,10 @@ export function App() {
4849
className="border-4 border-purple-950 px-2 py-1"
4950
/>
5051
</div>
52+
53+
<hr />
54+
55+
<FlipFlop />
5156
</div>
5257
);
5358
}

src/FlipFlip.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { useReducer, useEffect } from "react";
2+
import { ExtractByTag } from "./re-effect/type-utils";
3+
import { Data } from "effect";
4+
5+
type Actions<Result, Error> = Data.TaggedEnum<{
6+
Begin: {};
7+
Succeed: { readonly result: Result };
8+
Fail: { readonly error: Error };
9+
}>;
10+
11+
// Effect can generate the action creators (constructors) + matchers:
12+
const { Begin, Succeed, Fail, $match } = Data.taggedEnum<Actions<any, any>>();
13+
14+
type State = Data.TaggedEnum<{
15+
Empty: {};
16+
Failed: { readonly errorMessage: string };
17+
Completed: { readonly answer: number };
18+
}>;
19+
20+
// we'll have to alias $match
21+
const {
22+
Empty,
23+
Failed,
24+
Completed,
25+
$match: matchState,
26+
} = Data.taggedEnum<State>();
27+
28+
const actionToStateMatcher = $match({
29+
Begin: (_) => Empty(),
30+
Fail: ({ error }) => Failed({ errorMessage: String(error) }),
31+
Succeed: ({ result }) => Completed({ answer: 42 }),
32+
});
33+
34+
const reducer: React.Reducer<State, Actions<any, any>> = (_s, actions) =>
35+
actionToStateMatcher(actions);
36+
37+
const useFlipFlop = (dispatch: React.Dispatch<Actions<any, any>>) => {
38+
useEffect(() => {
39+
dispatch(Begin());
40+
let flip = false;
41+
42+
const interval = setInterval(() => {
43+
dispatch(flip ? Succeed({ result: "flip" }) : Fail({ error: "flop" }));
44+
flip = !flip;
45+
}, 600);
46+
47+
return () => clearTimeout(interval);
48+
}, []);
49+
};
50+
51+
const INITIAL_STATE = Empty();
52+
53+
type CompletedCase = ExtractByTag<State, "Completed">;
54+
55+
const DisplayCompleted = ({ answer }: CompletedCase) => (
56+
<h1 className="text-2xl">
57+
👉<span className="text-teal-700 underline">{answer}</span>👈
58+
</h1>
59+
);
60+
61+
const DisplayEmpty = () => <div>emptiness...</div>;
62+
63+
type FailedCase = ExtractByTag<State, "Failed">;
64+
const DisplayFailed = ({ errorMessage }: FailedCase) => (
65+
<p className="text-orange-400">{errorMessage}</p>
66+
);
67+
68+
const StateMatcher = matchState({
69+
Empty: DisplayEmpty,
70+
Failed: DisplayFailed,
71+
Completed: DisplayCompleted,
72+
});
73+
74+
export const FlipFlop = () => {
75+
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
76+
77+
useFlipFlop(dispatch);
78+
79+
return StateMatcher(state);
80+
};

0 commit comments

Comments
 (0)