Skip to content

Commit

Permalink
feat(core/presentation): Add useDebouncedValue react hook
Browse files Browse the repository at this point in the history
renamed hooks files to '.hook.ts' and exported all from an index.ts
  • Loading branch information
christopherthielen committed Oct 18, 2019
1 parent 6186e40 commit b384038
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 9 deletions.
5 changes: 3 additions & 2 deletions app/scripts/modules/core/src/presentation/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './useLatestPromise';
export * from './useData';
export * from './useData.hook';
export * from './useDebouncedValue.hook';
export * from './useLatestPromise.hook';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IPromise } from 'angular';
import { $q } from 'ngimport';
import { isNil } from 'lodash';

import { useLatestPromise, IUseLatestPromiseResult } from './useLatestPromise';
import { useLatestPromise, IUseLatestPromiseResult } from './useLatestPromise.hook';

/**
* A react hook which invokes a promise returning callback whenever any of its dependencies changes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from 'react';

/**
* A react hook which returns a debounced value
*
* @param value the immediate value
* @param debounceMs: the debounce time, in ms
*/
export function useDebouncedValue<T>(value: T, debounceMs: number): [T, boolean] {
const [debouncedValue, setDebouncedValue] = useState(value);
const [isDebouncing, setIsDebouncing] = useState(false);
useEffect(() => setIsDebouncing(value !== debouncedValue));
useEffect(() => {
const id = setTimeout(() => setDebouncedValue(value), debounceMs);
return () => clearTimeout(id);
}, [value, debounceMs]);

return [debouncedValue, isDebouncing];
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,34 @@ export interface IUseLatestPromiseResult<T> {
* @returns an object with the result and current status of the promise
*/
export function useLatestPromise<T>(callback: () => IPromise<T>, deps: DependencyList): IUseLatestPromiseResult<T> {
const mounted = useRef(false);
const isMounted = useRef(false);
const requestInFlight = useRef<IPromise<T>>();
const [status, setStatus] = useState<IRequestStatus>('NONE');
const [result, setResult] = useState<T>();
const [error, setError] = useState<any>();
const [requestId, setRequestId] = useState(0);

// Starts a new request (runs the callback again)
const refresh = () => setRequestId(currentRequestId => currentRequestId + 1);
const refresh = () => setRequestId(id => id + 1);

// refresh whenever any dependency in the dependency list changes
useEffect(() => refresh(), deps);
useEffect(() => {
if (isMounted.current) {
refresh();
}
}, deps);

// Manage the mount/unmounted state
useEffect(() => {
mounted.current = true;
return () => (mounted.current = false);
isMounted.current = true;
return () => (isMounted.current = false);
}, []);

// Invokes the callback and manages its lifecycle.
// This is triggered when the requestId changes
useEffect(() => {
const promise = callback();
const isCurrent = () => mounted.current === true && promise === requestInFlight.current;
const isCurrent = () => isMounted.current === true && promise === requestInFlight.current;

// If no promise is returned from the callback, noop this effect.
if (!promise) {
Expand Down

0 comments on commit b384038

Please sign in to comment.