Skip to content

Commit

Permalink
Add flag to useTrackedPromise
Browse files Browse the repository at this point in the history
The new flag will determine if the callbacks will trigger or the errors
will throw if the component using the hook is not mounted. By default
they will only trigger if it is.
  • Loading branch information
afgomez authored and Alejandro Fernández Gómez committed Oct 19, 2020
1 parent cae268f commit 544e1dc
Showing 1 changed file with 31 additions and 6 deletions.
37 changes: 31 additions & 6 deletions x-pack/plugins/infra/public/utils/use_tracked_promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

/* eslint-disable max-classes-per-file */

import { DependencyList, useEffect, useMemo, useRef, useState } from 'react';
import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { useMountedState } from 'react-use';

interface UseTrackedPromiseArgs<Arguments extends any[], Result> {
createPromise: (...args: Arguments) => Promise<Result>;
onResolve?: (result: Result) => void;
onReject?: (value: unknown) => void;
cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never';
triggerOrThrow?: 'always' | 'whenMounted';
}

/**
Expand Down Expand Up @@ -64,16 +66,37 @@ interface UseTrackedPromiseArgs<Arguments extends any[], Result> {
* The last argument is a normal React hook dependency list that indicates
* under which conditions a new reference to the configuration object should be
* used.
*
* The `onResolve`, `onReject` and possible uncatched errors are only triggered
* if the underlying component is mounted. To ensure they always trigger (i.e.
* if the promise is called in a `useLayoutEffect`) use the `triggerOrThrow`
* attribute:
*
* 'whenMounted': (default) they are called only if the component is mounted.
*
* 'always': they always call. The consumer is then responsible of ensuring no
* side effects happen if the underlying component is not mounted.
*/
export const useTrackedPromise = <Arguments extends any[], Result>(
{
createPromise,
onResolve = noOp,
onReject = noOp,
cancelPreviousOn = 'never',
triggerOrThrow = 'whenMounted',
}: UseTrackedPromiseArgs<Arguments, Result>,
dependencies: DependencyList
) => {
const isComponentMounted = useMountedState();
const shouldTriggerOrThrow = useCallback(() => {
switch (triggerOrThrow) {
case 'always':
return true;
case 'whenMounted':
return isComponentMounted();
}
}, [isComponentMounted, triggerOrThrow]);

/**
* If a promise is currently pending, this holds a reference to it and its
* cancellation function.
Expand Down Expand Up @@ -144,7 +167,7 @@ export const useTrackedPromise = <Arguments extends any[], Result>(
(pendingPromise) => pendingPromise.promise !== newPendingPromise.promise
);

if (onResolve) {
if (onResolve && shouldTriggerOrThrow()) {
onResolve(value);
}

Expand Down Expand Up @@ -173,11 +196,13 @@ export const useTrackedPromise = <Arguments extends any[], Result>(
(pendingPromise) => pendingPromise.promise !== newPendingPromise.promise
);

if (onReject) {
onReject(value);
}
if (shouldTriggerOrThrow()) {
if (onReject) {
onReject(value);
}

throw value;
throw value;
}
}
),
};
Expand Down

0 comments on commit 544e1dc

Please sign in to comment.