Skip to content

Instantly share code, notes, and snippets.

@sagarpanchal
Created January 6, 2022 08:22
Show Gist options
  • Save sagarpanchal/b0bd4acf05094864c5b4e4d8f52abcb7 to your computer and use it in GitHub Desktop.
Save sagarpanchal/b0bd4acf05094864c5b4e4d8f52abcb7 to your computer and use it in GitHub Desktop.
ES Utils
import cloneDeep from 'lodash/cloneDeep';
import forOwn from 'lodash/forOwn';
import isEqual from 'lodash/isEqual';
import memoize from 'lodash/memoize';
import xorWith from 'lodash/xorWith';
import { DateTime, Duration } from 'luxon';
export const LOCALE = 'en-US';
export const CURRENCY = 'USD';
export const TIMEZONE_IANA = 'Asia/Kolkata';
export const LUXON_FORMAT = { DATE: 'dd/LL/y', TIME: 'hh:mm a', DATE_TIME: 'dd/LL/y hh:mm a', DURATION: 'hh:mm:ss' };
export const REGEX = { NUMERIC: { STRICT: /^[+-]?[\d]+[.]?[\d]*$/gm, LOOSE: /^[+-]?[\d]*[.]?[\d]*$/gm } };
export const FRACTION_LENGTH = 2;
export const runInDevelopment = (callback) =>
[undefined, '', 'development'].includes(process.env.REACT_APP_ENV) && callback();
export const logInfo = (...args) => runInDevelopment(() => console.info(...args)); // eslint-disable-line no-console
export const logWarn = (...args) => runInDevelopment(() => console.warn(...args)); // eslint-disable-line no-console
export const logError = (...args) => runInDevelopment(() => console.error(...args)); // eslint-disable-line no-console
export const logTable = (...args) => runInDevelopment(() => console.table(...args)); // eslint-disable-line no-console
export const typeOf = (input, type) => input?.constructor?.name === (type ?? null);
export const catchError = (func, onError) => {
const handleError = (error) => {
logWarn(error);
return onError?.(error);
};
try {
const output = func?.();
if (!typeOf(output, 'Promise')) return output;
return output?.catch?.(handleError);
} catch (error) {
handleError(error);
}
};
export const isArray = (input) => typeOf(input, 'Array');
export const isObject = (input) => typeOf(input, 'Object');
export const isBoolean = (input) => typeOf(input, 'Boolean');
export const isString = (input) => typeOf(input, 'String');
export const isNumber = (input) => typeOf(input, 'Number') && !Number.isNaN(input) && Number.isFinite(input);
export const isNumeric = (input, strict = false) =>
new RegExp(strict ? REGEX.NUMERIC.STRICT : REGEX.NUMERIC.LOOSE).test(input);
export const isAlphaNumeric = (input) => new RegExp(REGEX.ALPHA_NUMERIC).test(input);
export const isFunction = (input) => typeOf(input, 'Function');
export const callFunction = (func, ...args) => isFunction(func) && func(...args);
export const forEach = (instance, callback) => Array.prototype.forEach.call(instance, callback);
export const isHTMLElement = (input) => input instanceof HTMLElement;
export const isEmpty = (input, options) => {
options = { isEmpty: [], isNotEmpty: [], ...options };
if (options.isEmpty?.includes?.(input)) return true;
if (options.isNotEmpty?.includes?.(input)) return false;
if ([undefined, null].includes(input)) return true;
if (input?.constructor?.name === 'Array') return !input.length;
if (input?.constructor?.name === 'Number') return Number.isNaN(input);
if (input?.constructor?.name === 'Object') return !Object.keys(input).length;
if (input?.constructor?.name === 'String') return !input.trim().length;
return false;
};
export const isNotEmpty = (...args) => !isEmpty(...args);
export const pruneEmpty = (obj, options) => {
const prune = (current) => {
forOwn(current, (value, key) => {
if (isEmpty(value, options) || ((isObject(value) || isArray(value)) && isEmpty(prune(value), options)))
delete current[key];
});
if (isArray(current)) current = current.filter(isNotEmpty);
return current;
};
return prune(cloneDeep(obj));
};
export const isArrayEqual = (list1, list2) => isEmpty(xorWith(list1, list2, isEqual));
export const returnIfNotEmpty = (value, replaceWith) => (isEmpty(value) ? replaceWith : value);
export const hasKey = (object, key) => isObject(object) && !isEmpty(object) && Object.keys(object).includes(key);
export const withDefaults = (object, defaults) => ({ ...defaults, ...object });
export const getCurrentTime = () => DateTime.local().setZone(TIMEZONE_IANA);
export const formatDateTime = memoize(
(isoDate, format = LUXON_FORMAT.DATE_TIME) => {
const dateTime = DateTime.fromISO(isoDate);
return dateTime?.isValid ? dateTime.toFormat(format) : undefined;
},
(...args) => JSON.stringify(args),
);
export const formatDate = (isoDate, format = LUXON_FORMAT.DATE) => formatDateTime(isoDate, format);
export const formatTime = (isoDate, format = LUXON_FORMAT.TIME) => formatDateTime(isoDate, format);
export const formatDuration = (duration, format = LUXON_FORMAT.DURATION) =>
Duration.isDuration(duration) && duration?.isValid ? duration.toFormat(format) : undefined;
export const getDateTimeDiff = (startISO, endISO) => DateTime.fromISO(endISO).diff(DateTime.fromISO(startISO));
export const getFormattedDateTimeDiff = (startISO, endISO, format = LUXON_FORMAT.DURATION) =>
formatDuration(getDateTimeDiff(startISO, endISO), format);
export const formatNumber = (input, options = {}) => {
if (!isNumber(Number(input))) return input;
const { locale, trimFractions, ...rest } = { locale: LOCALE, trimFractions: false, ...options };
const fractionLength = trimFractions ? rest?.fractionLength : `${input}`.split('.')?.[1]?.length;
const defaults = { maximumFractionDigits: fractionLength, minimumFractionDigits: fractionLength };
return new Intl.NumberFormat(locale, { ...defaults, ...rest }).format(input);
};
export const formatCurrency = (number, options = {}) => {
const { locale, ...rest } = { locale: LOCALE, currency: CURRENCY, ...options };
return new Intl.NumberFormat(locale, { style: 'currency', ...rest }).format(number);
};
export const formatDecimal = (input, fractionLength = FRACTION_LENGTH) =>
formatNumber(input, { trimFractions: true, fractionLength });
export const parseDecimal = (input, fractionLength = FRACTION_LENGTH) =>
Number(parseFloat(input).toFixed(fractionLength));
export const formatFloat = (input, fractionLength = FRACTION_LENGTH) => parseFloat(input).toFixed(fractionLength);
export const formatInlineList = (value, options = {}) => {
if (!isString(value)) return value;
const { separator, allowAppend } = { separator: ',', allowAppend: false, ...options };
const valueList = `${value}`.replace(/[\s,]+/gm, separator).split(separator);
return valueList
.filter((value, index) => !isEmpty(value) || (allowAppend && index && valueList?.length === index + 1))
.join(separator);
};
export const sortEntriesByKey = (desc = false) => {
const n = { less: desc ? 1 : -1, more: desc ? -1 : 1 };
return (curr, next) => (curr?.[0] < next?.[0] ? n.less : curr?.[0] > next?.[0] ? n.more : 0);
};
export const sortArrayByKey = (key = 'id', desc = false) => {
if (!isString(key)) return undefined;
const n = { less: desc ? 1 : -1, more: desc ? -1 : 1 };
return (curr, next) => (curr?.[key] < next?.[key] ? n.less : curr?.[key] > next?.[key] ? n.more : 0);
};
export const reduceUnique = (key) => {
return (a = [], c) => {
const indexFound = a.findIndex((item) => (key === undefined ? item === c : item[key] === c[key]));
if (indexFound === -1) a.push(c);
return a;
};
};
export const padArray = (list, length, fillWith) => {
return list.concat(Array(length).fill(fillWith)).slice(0, length);
};
export const reduceTotal = (list, key) => {
if (!isArray(list) || isEmpty(list)) return 0;
const numList = key === undefined ? list.map(Number) : list.map((item) => Number(item?.[key]));
return numList.filter(isNumber).reduce((pv, cv) => (pv += cv), 0);
};
export const classNames = (list) => list.filter(isString).join(' ');
export const upperFirst = (input, locale = LOCALE) =>
isString(input) ? input.replace(/(^[a-z])/, (match) => match.toLocaleUpperCase(locale)) : input;
export const lowerFirst = (input, locale = LOCALE) =>
isString(input) ? input.replace(/(^[a-z])/, (match) => match.toLocaleLowerCase(locale)) : input;
export const upperCase = (input, locale = LOCALE) => (isString(input) ? input.toLocaleUpperCase(locale) : input);
export const lowerCase = (input, locale = LOCALE) => (isString(input) ? input.toLocaleLowerCase(locale) : input);
export const titleCase = (input, locale = LOCALE) =>
catchError(
() =>
isString(input)
? input
.split(/([ :–—-])/)
.map((current, index, list) => {
return (
// Check for small words
current.search(/^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|v.?|vs.?|via)$/i) > -1 &&
// Skip first and last word
index !== 0 &&
index !== list.length - 1 &&
// Ignore title end and subtitle start
list[index - 3] !== ':' &&
list[index + 1] !== ':' &&
// Ignore small words that start a hyphenated phrase
(list[index + 1] !== '-' || (list[index - 1] === '-' && list[index + 1] === '-'))
? current.toLocaleLowerCase(locale)
: current.substr(1).search(/[A-Z]|\../) > -1 // Ignore intentional capitalization
? current
: list[index + 1] === ':' && list[index + 2] !== '' // Ignore URLs
? current
: current.replace(/([A-Za-z0-9\u00C0-\u00FF])/, (match) => match.toLocaleUpperCase(locale)) // Capitalize the first letter
);
})
.join('')
: input,
() => '',
);
export const objectToQueryString = (object) =>
catchError(
() =>
`?${Object.entries(object)
.map(([key, value]) => `${key}=${!isEmpty(value) && isFunction(value?.toString) ? value.toString() : ''}`)
.join('&')}`,
() => '',
);
export const queryStringToObject = (search = window.location.search) =>
catchError(
() => {
const urlParams = new URLSearchParams(search);
return Object.fromEntries(urlParams.entries());
},
() => {},
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment