t-hooks

T-Hooks provides a set of react hooks built upon T-Tasks library. Using task hooks instead of conventional lifecycle hooks alows easier usage of asynchronous operations withing hooks and provides automatic operation cancelation in case of hook unmounting

Usage no npm install needed!

<script type="module">
  import tHooks from 'https://cdn.skypack.dev/t-hooks';
</script>

README

CI

T-Hooks task-based React hook library

T-Hooks provides a set of react hooks built upon T-Tasks library. Using task hooks instead of conventional lifecycle hooks alows easier usage of asynchronous operations withing hooks and provides automatic operation cancelation in case of hook unmounting or re-render.

Task-based useEffect

In this scenario, every time arg updates an asynchronous operation is started, resulting in a side effect. If hook is unmonted before the operation finishes no side effect is performed, preventing modification of state of unmounted component. Moreover, if arg changes before the operation finishes another operation in started instead, canceling the previous one.

const taskCreator = (arg: string) => Task.fromPromise(someAsyncOperation(arg)).tap((result) => {
  setSomething(result); // side effects
});

useTaskEffect(() => taskCreator(arg), [arg]);

Generator-based useEffect

In this scenario a generator syntax is used to achieve the same result. Generator syntax allows for more natural task chaining via yield syntax. Note that despite the fact that a task may be yielded directly as yield someTask the result of such expression may be unknown in case of multiple yield statements, so using yield* someTask.generator() is recommended.

useGeneratorEffect(function*() {
  const result = yield* Task.fromPromise(someAsyncOperation(arg)).generator();

  setSomething(result); // side effects
}, [arg]);

Chaining multiple effects

In this scenario generator syntax is used to chain two consequtive async operations with two side-effects. In case of unmounting or re-render during execution of the first operation, the second one would not be started and both side effects would not be performed. In case of unmounting or re-render during execution of the second operation, only the second side-effect would be prevented.

useGeneratorEffect(function*() {
  const result = yield* Task.fromPromise(someAsyncOperation(arg)).generator();

  setSomething(result1); // side effect 1

  const result2 = yield* Task.fromPromise(otherAsyncOperation(resilt1)).generator();

  setSomethingElse(result2); // side effect 2
}, [arg]);

Task-based useMemo

In this scenario, every time arg updates an asynchronous operation is started, updating userData asynchronously. In case of hook unmounting or updating arg again before the previous operation finishes, another operation in started instead, canceling the previous one.

const userData = useTaskMemo(null, () => Task.fromPromise(getUserDataAsync(userId)), [userId]);

Generator-based useMemo

In this scenario a generator syntax is used to achieve the same result. Generator syntax allows for more natural task chaining via yield syntax. Note that despite the fact that a task may be yielded directly as yield someTask the result of such expression may be unknown in case of multiple yield statements, so using yield* someTask.generator() is recommended.

const userData = useGeneratorMemo(null, function*() {
  return yield* Task.fromPromise(getUserDataAsync(userId)).generator();
}, [userId]);

Chaining multiple transformations

In this scenario a generator syntax is used to chain two consequtive async operations in a single task memo.

const usageStats = useGeneratorMemo(null, function*() {
  const userData = yield* Task.fromPromise(getUserDataAsync(userId)).generator();

  yield* timeoutTask(100).generator(); // one may add delays easily

  return yield* Task.fromPromise(getUsageStats(userData)).generator();
}, [userId]);

Task-based useCallback

In this scenario an asynchrounous callback is created, performing side-effects only if the hook is not unmounted. Callback is also interrupted if invoked again

const taskCreator = (userId: string) => liftResult(getUserDataAsync(arg)).tap((userData) => {
  setUserData(userData); // side effects
});

const onClick = useTaskCallback(() => taskCreator(userId)), [userId]);

return <button onClick={onClick}>Click me</button>

Generator-based useCallback

In this scenario an asynchrounous callback is created via generator syntax

const onClick = useGeneratorCallback(function*() {
  const userData = yield* Task.fromPromise(getUserDataAsync(userId)).generator();

  yield* timeoutTask(100).generator(); // one may add delays easily

  setUserData(userData); // side-effects
}), [userId]);

return <button onClick={onClick}>Click me</button>