UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

244 lines (207 loc) • 6.93 kB
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);