aboutsummaryrefslogtreecommitdiff
path: root/src/ffi.mjs
diff options
context:
space:
mode:
authorKero van Gelder <keroami@users.noreply.github.com>2023-04-05 14:33:00 +0200
committerGitHub <noreply@github.com>2023-04-05 13:33:00 +0100
commita1b2f0344a59e24ae4d16f1689a02ba72ced2b53 (patch)
tree83843c6afd048b2c770a811696e1947bf915ffaa /src/ffi.mjs
parent2feb2a8e1a427cb7545785e2678e4523533c432e (diff)
downloadlustre-a1b2f0344a59e24ae4d16f1689a02ba72ced2b53.tar.gz
lustre-a1b2f0344a59e24ae4d16f1689a02ba72ced2b53.zip
🐛 Avoid a race condition when dispatch is used twice from outside (#8)
* Upgrade examples to lustre 2.0 * Upgrade to gleam 0.27.0 * Fix: avoid a race condition when dispatch is used twice from outside E.g. websocket open + websocket msg arriving Application returns a Cmd from update(model, msg) but the Effect handler is *not* ran be React 18 before the websocket msg-arriving is handled by the Reducer. Result: The Cmd returned by update(model, msg) is dropped silently. --------- Co-authored-by: Kero van Gelder <kero@chmeee.org>
Diffstat (limited to 'src/ffi.mjs')
-rw-r--r--src/ffi.mjs29
1 files changed, 23 insertions, 6 deletions
diff --git a/src/ffi.mjs b/src/ffi.mjs
index cb458d4..b6883d4 100644
--- a/src/ffi.mjs
+++ b/src/ffi.mjs
@@ -35,20 +35,37 @@ export const mount = ({ init, update, render }, selector) => {
//
let dispatch = null;
+ let init_cmds = null;
+
const App = React.createElement(() => {
- const [[state, cmds], $dispatch] = React.useReducer(
- ([state, _], action) => update(state, action),
- init
+ const [state, $dispatch] = React.useReducer(
+ (state, action) => {
+ let [new_state, cmds] = update(state, action)
+ // Handle cmds immediately, they're not React-kind-of-state
+ for (const cmd of Cmd.to_list(cmds)) {
+ cmd(dispatch);
+ }
+ return new_state
+ },
+ undefined,
+ () => {
+ let [state, cmds] = init
+ // postpone handling cmds, as we do not have the dispatch, yet
+ init_cmds = cmds
+ return state
+ }
);
- const el = render(state);
if (dispatch === null) dispatch = $dispatch;
+ const el = render(state);
+
React.useEffect(() => {
- for (const cmd of Cmd.to_list(cmds)) {
+ for (const cmd of Cmd.to_list(init_cmds)) {
cmd($dispatch);
}
- }, [cmds]);
+ init_cmds = undefined; // Just so we get an error, rather than re-execute
+ }, []); // empty deps array means this effect will only run on initial render
return typeof el == "string" ? el : el($dispatch);
});