From 687aec7464290ffabde4bbc724d2ec81047145d5 Mon Sep 17 00:00:00 2001
From: Taras Mankovski
Date: Mon, 15 Jan 2024 15:47:41 -0500
Subject: [PATCH] Added a way to stop the operation
---
src/App.tsx | 17 +++++++++++++++-
src/components/LoadingSpinner.tsx | 6 ++++++
src/hooks/useLoader.ts | 32 +++++++++++++++++++++++++------
src/operations/UpdateContext.ts | 15 +++++++++------
src/operations/createLoader.ts | 20 +++++++++++++++++--
5 files changed, 75 insertions(+), 15 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index b4c4f65..5f15a72 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,5 @@
import "./App.css";
-import { sleep, Operation } from "effection";
+import { sleep, Operation, run } from "effection";
import { Player } from "./components/Player";
import { useState } from "react";
@@ -174,6 +174,21 @@ export const App = () => {
{...options}
/>
+
+
+
>
);
diff --git a/src/components/LoadingSpinner.tsx b/src/components/LoadingSpinner.tsx
index 667749a..5c2fa8e 100644
--- a/src/components/LoadingSpinner.tsx
+++ b/src/components/LoadingSpinner.tsx
@@ -39,6 +39,12 @@ export function LoadingSpinner({ loader }: { loader: LoaderState }): JS
Failed after 3 attempts. Please contact support.
)
+ case "stopped":
+ return (
+
+ {loader.reason}
+
+ )
}
return <>>
diff --git a/src/hooks/useLoader.ts b/src/hooks/useLoader.ts
index 5df5ff7..b6e4d09 100644
--- a/src/hooks/useLoader.ts
+++ b/src/hooks/useLoader.ts
@@ -1,7 +1,7 @@
import { useMemo, useEffect, useState, useCallback } from "react";
-import { run, type Callable } from "effection";
+import { createChannel, run, spawn, type Callable, each, race, Operation } from "effection";
import { CreateLoaderOptions, createLoader } from "../operations/createLoader";
-import { setUpdateContext, update } from "../operations/UpdateContext";
+import { setUpdateContext } from "../operations/UpdateContext";
export type LoaderState =
| {
@@ -33,11 +33,16 @@ export type LoaderState =
| {
type: "failed";
error: Error;
- };
+ }
+ | {
+ type: "stopped";
+ reason: string;
+ };
export type LoaderFn = (params: {
attempt: number;
signal: AbortSignal;
+ stop: (reason: string) => Operation;
}) => Callable;
export type UseLoaderOptions = Partial, "load">>;
@@ -86,9 +91,24 @@ export function useLoader(
useEffect(() => {
const task = run(function* () {
- yield* setUpdateContext(setState);
- yield* update({ type: "initial" })
- yield* loader();
+ const channel = createChannel>();
+
+ yield* setUpdateContext(channel.send);
+
+ const updates = yield* spawn(function*() {
+ for (const value of yield* each(channel)) {
+ setState(value);
+ if (value.type === "stopped") {
+ throw new Error("Forced interrupt")
+ }
+ yield* each.next();
+ }
+ });
+
+ const driver = yield* spawn(loader);
+
+ yield* race([updates, driver]);
+
});
return () => {
diff --git a/src/operations/UpdateContext.ts b/src/operations/UpdateContext.ts
index 942ce05..e82a6ee 100644
--- a/src/operations/UpdateContext.ts
+++ b/src/operations/UpdateContext.ts
@@ -1,13 +1,16 @@
-import { Operation, createContext, lift } from "effection";
+import { Operation, createContext, suspend } from "effection";
import { LoaderState } from "../hooks/useLoader";
-export const UpdateContext = createContext("update");
+export const UpdateContext = createContext<(value: LoaderState) => Operation>("update");
export const update = function* update(value: LoaderState): Operation {
- const setState = yield* UpdateContext;
- yield* setState(value);
+ const send = yield* UpdateContext;
+ yield* send(value);
+ if (value.type === "stopped") {
+ yield* suspend();
+ }
}
-export function* setUpdateContext(setState: (value: LoaderState) => void): Operation {
- yield* UpdateContext.set(lift(setState));
+export function* setUpdateContext(send: (value: LoaderState) => Operation): Operation {
+ yield* UpdateContext.set(send);
}
\ No newline at end of file
diff --git a/src/operations/createLoader.ts b/src/operations/createLoader.ts
index 5ef1707..4995911 100644
--- a/src/operations/createLoader.ts
+++ b/src/operations/createLoader.ts
@@ -1,5 +1,12 @@
/* eslint-disable react-hooks/rules-of-hooks */
-import { Operation, sleep, spawn, call, useAbortSignal } from "effection";
+import {
+ Operation,
+ sleep,
+ spawn,
+ call,
+ useAbortSignal,
+ useScope,
+} from "effection";
import { CreateSpinnerOptions, createSpinner } from "./createSpinner";
import { update } from "./UpdateContext";
import { LoaderFn } from "../hooks/useLoader";
@@ -26,6 +33,8 @@ export function createLoader({
type: "started",
});
+ const scope = yield* useScope();
+
for (let attempt = 0; attempt <= retryAttempts; attempt++) {
const spinner = yield* spawn(function* () {
if (attempt === 0) {
@@ -40,7 +49,14 @@ export function createLoader({
const signal = yield* useAbortSignal();
try {
- const result = yield* call(() => load({ attempt, signal }));
+ const result = yield* call(() =>
+ load({
+ attempt,
+ signal,
+ stop: (reason: string) =>
+ scope.run(() => update({ type: "stopped", reason })),
+ })
+ );
yield* update({
type: "success",