@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
244 lines (207 loc) • 6.93 kB
text/typescript
import * as R from "ramda";
import { useRef } from "react";
/**
*
* @param callback - The function to throttle.
* @param delay - The delay in milliseconds.
* @returns A throttled version of the callback.
*/
export const useThrottle = (callback: () => void, delay: number) => {
const lastCallRef = useRef<number>(0);
const timeoutRef = useRef<any | null>(null);
const throttledFunction = () => {
const now = Date.now();
if (now - lastCallRef.current >= delay) {
callback();
lastCallRef.current = now;
} else if (!timeoutRef.current) {
const remainingTime = delay - (now - lastCallRef.current);
timeoutRef.current = setTimeout(() => {
callback();
lastCallRef.current = Date.now();
timeoutRef.current = null;
}, remainingTime);
}
};
const cancel = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
return { throttledFunction, cancel };
};
/**
* standard debounce function.
* @param {Object} options
* @param {Function} options.fn function to debounce
* @param {Number} options.wait debounce duration in ms. defaults to 200
* @param {Boolean} options.immediate will trigger immediately instead of after wait
* @param {Object} options.context context to apply the functions on
* @param {Function} debounced function
*/
export function debounce({
fn,
wait = 200,
immediate = true,
context,
}: {
fn: Function;
wait?: number;
immediate?: boolean;
context?: any;
}) {
let timeout;
return function debounced(...args) {
const that = context || this;
function callLater() {
timeout = null;
if (!immediate) {
fn.apply(that, args);
}
}
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(callLater, wait);
if (callNow) {
fn.apply(that, args);
}
};
}
/**
* a function that does nothing
*/
export function noop() {
void 0;
}
/**
* returns if passed value is a function
* @param {*} functionToCheck to add the subscription feature on
* @returns {Bool} subscriber object
*/
export function isFunction(functionToCheck) {
return functionToCheck ? typeof functionToCheck === "function" : false;
}
export type ParametersExceptFirst<T extends (...args: any) => any> = T extends (
ignored: infer _,
...args: infer P
) => any
? P
: never;
type SubscriberFn<T> = (
event: string | number,
handler: (...args: any[]) => void
) => T;
export type EventHandlerFn<T> = (event: string, ...args: unknown[]) => T;
type SubscriberObj = Partial<{
on?: SubscriberFn<SubscriberObj>;
invokeHandler: EventHandlerFn<SubscriberObj>;
removeHandler: EventHandlerFn<SubscriberObj>;
handlers: Record<string, SubscriberFn<SubscriberObj>[] | []>;
}>;
export const clearObject = (object: { [key: string]: unknown }) =>
Object.keys(object).forEach((key) => delete object[key]);
/**
* returns a subscribe object to which handlers can be attached
* @param {Object} obj to add the subscription feature on
* @returns {Object} subscriber object
*/
export function subscriber(obj: SubscriberObj = {}): SubscriberObj {
if (!obj.handlers) {
obj.handlers = {};
}
const handlers = obj.handlers;
/**
* creates a dispose function to remove handlers on a given event
* @param {string} event to dispose handlers for
* @param {function} handler to remove
* @returns {function} to dispose handlers
*/
function dispose(event, handler) {
/**
* function to invoke to remove a handler on an event.
* handlers are invoked with this function as last argument
* @param {object} options
* @param {boolean} options.disposeAll if set to true, will
* remove all handlers on the object
*/
return function ({ disposeAll = false } = {}) {
if (disposeAll) {
clearObject(handlers);
return;
}
handlers[event] = R.reject(R.equals(handler), handlers[event] || []) as
| SubscriberFn<SubscriberObj>[]
| [];
};
}
/**
* adds a handler for a specific event
* @param {string} event to trigger the handler
* @param {function} handler to invoke
* @returns {object} returns `this` so that handlers declaration
* can be chained
*/
obj.on = function (event, handler) {
handlers[event] = R.append(handler, handlers[event] || []) as
| SubscriberFn<SubscriberObj>[]
| [];
return obj;
};
/**
* invokes handlers for a specific event with the provided args
* will append the dispose function as last argument to the handler
* @param {string} event for which handlers should be invoked
* @param {Array<Any>} args variadic list args of args to invoke the handlers with
* @returns {object} returns `this` so handlers invocation can be chained
*/
obj.invokeHandler = function (event: string, ...args) {
const callHandlerWithDispose = (handler) =>
handler(...args, dispose(event, handler));
R.compose(R.forEach(callHandlerWithDispose), R.propOr([], event))(handlers);
return obj;
};
obj.removeHandler = function (event, handler) {
handlers[event] = R.reject(R.equals(handler), handlers[event]);
return obj;
};
return obj;
}
/**
* tries to parse a json. if it fails returns the initial value
* @param {Any} data
* @returns {any}
*/
export const parseJsonIfNeeded = R.tryCatch(JSON.parse, R.flip(R.identity));
/**
* map function which works with async functions. Has the same signature as Ramda's map
* function, but allows async functions, and will return a promise
* @param {Function} fn function to run
* @param {[Any]} arr to run the async function unto
* @returns {Promise<[Any]>} a promise which resolves to an array
*/
export const mapAsync = R.curry(async (fn, arr: Promise<unknown>[]) => {
return await R.compose(
(promises: Promise<any>[]) => Promise.all(promises),
R.map(fn)
)(arr);
});
/**
* This function will determine if the given prop or path is present
* if present return that prop or path to the user
* When using a path, this function only accepts . as a delimiter i.e. extensions.free
* It is important to use currying when passing the args i.e. objectHasPropOrPath(arg1)(arg2)
* @param {String} property - i.e. property, this is what you want to pull from mapped object
* @param {Object} data - i.e. navigator?.screenData or any data you need to extract key from
* @param {Object} mappedObject - i.e mapping[i] the value of the mapped object
*/
export const extractDataFromProperty = (property, data): any =>
// TODO fix ramda types
// @ts-ignore
R.compose(
// @ts-ignore
R.ifElse(R.hasPath(R.__, data), R.path(R.__, data), R.always(null)),
R.tryCatch(R.split("."), R.always([])),
R.when(R.has(property), R.prop(property))
);
export const then = (fn) => (promise) => promise.then(fn);