A collection of JavaScript utilities.
npm install q-js-utilsA collection of JavaScript utilities.
``bash`
npm install q-js-utils
Or
`bash`
yarn add q-js-utils
For the entire library (convenience, but potentially larger bundle if not tree-shaken by consumer's bundler):
`ts
import { darkOrLight, str2Hex, getInitials } from 'q-js-utils';
// Or
// import * as utils from 'q-js-utils';
const isDark = darkOrLight('#000');
const nameToHex = str2Hex('Muhamad Husein');
const initialName = getInitials('Muhamad Husein');
console.log(isDark); // true
console.log(nameToHex); // f529de
console.log(initialName); // MH
`
For specific modules (recommended for tree-shaking):
`ts`
import { darkOrLight } from 'q-js-utils/darkOrLight';
import { str2Hex } from 'q-js-utils/str2Hex';
import { getInitials } from 'q-js-utils/getInitials';
---
`ts
import { isNumber } from 'q-js-utils/isNumber';
import { isNegative } from 'q-js-utils/isNegative';
import { padWithLeadingZeros } from 'q-js-utils/padWithLeadingZeros';
const one = 1;
const minus = -1;
console.log(isNumber(one)); // true
console.log(isNegative(one)); // false
console.log(isNegative(minus)); // true
console.log(padWithLeadingZeros(one)); // '01'
`
---
Debouncing an input for an autocomplete search
`ts
import { debounce } from 'q-js-utils/debounce';
const searchInput = document.getElementById('searchInput') as HTMLInputElement;
const performSearch = (query: string) => {
console.log(Performing search for: ${query});
// In a real app, you would make an API call here
};
const debouncedSearch = debounce(performSearch, 300); // Wait 300ms after last keypress
if (searchInput) {
searchInput.addEventListener('input', (event) => {
const target = event.target as HTMLInputElement;
debouncedSearch(target.value);
});
}
`
Debouncing a window resize event
`ts
import { debounce } from 'q-js-utils/debounce';
const handleResize = () => {
console.log(Window resized to: ${window.innerWidth}x${window.innerHeight});`
};
// Wait 250ms after resizing stops
const debouncedResize = debounce(handleResize, 250);
window.addEventListener('resize', debouncedResize);
---
Resetting a Form Before Debounced Submission
`ts
import { debounceAdvanced } from 'q-js-utils/debounceAdvanced';
const submitForm = debounceAdvanced(
(formData: { email: string }) => console.log("Submitting:", formData),
1000
);
// User clicks submit but then clicks "Reset"
submitForm({ email: "user@example.com" });
submitForm.cancel(); // Stops the submission if the form was reset
`
Avoiding Unnecessary Resize/Scroll Handlers
`ts
import { debounceAdvanced } from 'q-js-utils/debounceAdvanced';
const logScrollPosition = debounceAdvanced(
() => console.log("Current scroll Y:", window.scrollY),
200
);
window.addEventListener("scroll", logScrollPosition);
// When navigating away, cancel pending calls
window.removeEventListener("scroll", logScrollPosition);
logScrollPosition.cancel(); // Cleanup
`
Game Input Handling (Cancel on Player Death)
`ts
import { debounceAdvanced } from 'q-js-utils/debounceAdvanced';
const shootBullet = debounceAdvanced(
() => console.log("🔥 Pew!"),
300,
{ leading: true } // Immediate first shot
);
shootBullet(); // Fires instantly
shootBullet(); // Queued for 300ms later...
// Player dies before the next shot
shootBullet.cancel(); // Prevents queued shots
`
---
Basic usage
`ts
import { throttle } from 'q-js-utils/throttle';
const throttledScroll = throttle((position: number) => {
console.log('Current scroll position:', position);
}, 100);
window.addEventListener('scroll', () => throttledScroll(window.scrollY));
`
With default wait time (300ms)
`ts
import { throttle } from 'q-js-utils/throttle';
const throttledClick = throttle(() => console.log('Clicked!'));
button.addEventListener('click', throttledClick);
`
---
Cancellation Support (cancel())
`ts
import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';
const throttledScroll = throttleAdvanced(handleScroll, 100);
window.addEventListener('scroll', throttledScroll);
// Later...
throttledScroll.cancel(); // Stops any pending execution
`
Immediate Execution (flush())
`ts
import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';
const throttledApiCall = throttleAdvanced(fetchData, 1000);
throttledApiCall("query");
// Force execute if waiting
throttledApiCall.flush();
`
Pending Check (pending())
`ts`
if (throttledFn.pending()) {
console.log("Waiting to execute...");
}
Cancellable Scroll Handler
`ts
import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';
const scrollHandler = throttleAdvanced((position: number) => {
console.log("Current scroll:", position);
}, 200);
window.addEventListener('scroll', () => scrollHandler(window.scrollY));
// When leaving the page
window.addEventListener('beforeunload', () => {
scrollHandler.cancel(); // Cleanup
});
`
Throttled API Calls with Manual Flush
`ts
import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';
const searchAPI = throttleAdvanced(async (query: string) => {
const results = await fetch(/search?q=${query});
displayResults(results);
}, 500);
searchInput.addEventListener('input', (e) => {
searchAPI(e.target.value);
});
// "Search Now" button forces execution
searchButton.addEventListener('click', () => {
searchAPI.flush();
});
`
Game Loop with Cancellation
`ts
import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';
const gameUpdate = throttleAdvanced((deltaTime: number) => {
updatePlayerPosition(deltaTime);
}, 16); // ~60fps
gameLoop.on('update', gameUpdate);
// When game pauses
pauseButton.addEventListener('click', () => {
gameUpdate.cancel(); // Stop pending updates
});
`
---
`ts
import { request } from 'q-js-utils/request';
try {
const todo = await request('https://jsonplaceholder.typicode.com/todos/1');
console.log('request Todo:', todo);
} catch (error) {
console.error('request Error:', error);
}
console.log('Download Progress');
try {
const blob = await request('https://httpbin.org/bytes/1048576', {
// @ts-ignore
// responseType: "hell",
responseType: "blob",
onProgress: (progress) => {
console.log('progress: ', progress);
if (progress.total) {
console.log(
Progress: ${progress.loaded} / ${progress.total} bytes (${(
progress.progress! * 100
).toFixed(2)}%),Progress: ${progress.loaded} bytes loaded (total unknown)
);
} else {
console.log();
}
},
});
console.log('Download blob: ', blob);
console.log('Download complete. Blob size: ', blob?.size, 'bytes');
} catch (error: any) {
console.error('4. Download Progress Error: ', error);
for(let err in error){
console.error('err: ', err);
}
}
`
---
`ts
import { nextId } from '../src/nextId';
console.log(nextId to ${nextId()});nextId to ${nextId()}
console.log();nextId to ${nextId('x')}
console.log();`
---
`ts
import { cache } from 'q-js-utils/cache';
const sayHi = cache(name => 'Hi, ' + name);
`
---
`ts
import { darkOrLight } from 'q-js-utils/darkOrLight';
const isDark = darkOrLight('#000');
`
---
`ts
import { str2Hex } from 'q-js-utils/str2Hex';
const nameToHex = str2Hex('Muhamad Husein');
`
---
`ts
import { getInitials } from 'q-js-utils/getInitials';
const initialName = getInitials('Muhamad Husein');
`
---
`ts
import { obj2FormData } from 'q-js-utils/obj2FormData';
const objData = {
name: "Muhamad Husein",
email: "m.husein27@gmail.com"
};
const dataForm = obj2FormData(objData);
`
---
Can be significantly faster than Lodash's _.isEqual() in most cases, while still handling all the same edge cases.
`ts
import { isEqual } from 'q-js-utils/isEqual';
isEqual({ a: 1, b: [2, 3] }, { a: 1, b: [2, 3] }) // true
isEqual({ a: 1 }, { a: 1, b: undefined }) // false
isEqual({ a: 1, b: [2, 3] }, { b: [2, 3], a: 1 }) // true
isEqual(NaN, NaN) // true
`
not render in node).`ts
import { cn } from 'q-js-utils/cn';const isActive = true;
const hasError = false;
const emptyString = '';
const zero = 0;
const nullVar = null;
const undefinedConst = undefined;
let undefinedLet;
// Returns: "btn active" (when isActive is true and hasError is false)
cn('btn', isActive && 'active', hasError && 'error');
// undefined
cn(
hasError && 'error',
emptyString && 'emptyString',
zero && 'zero',
nullVar && 'nullVar',
undefinedConst && 'undefinedConst',
undefinedLet && 'undefinedLet',
);
`$3
Generate a UUIDv7 (time-ordered, monotonic).
RFC 9562: https://www.rfc-editor.org/rfc/rfc9562.html`ts
import { uuidv7 } from 'q-js-utils/uuidv7';// Lexicographically sorted even if generated in the same millisecond.
console.log('1. uuidv7:', uuidv7());
console.log('2. uuidv7:', uuidv7());
console.log('3. uuidv7:', uuidv7());
// Example output:
// 018f3fcd-04d1-7a8f-9f4a-1b33a5a2f7c9
// 018f3fcd-04d1-7a90-8a77-3a13f20dcbae
// 018f3fcd-04d1-7a91-81df-1d92ff45a4ec
`$3
Triggers a file download from a given Blob or File object in modern browsers.`ts
import { download } from 'q-js-utils/download';// Basic
download(text);
// Custom filename
download(text, {
name: "greeting.txt"
});
// Custom timeout to cleanup createObjectURL
download(text, {
timeout: 1000,
});
// Whether to append anchor to DOM for Safari compatibility
download(text, {
append: true,
});
// All
download(text, {
name: "greeting.txt",
timeout: 1000,
append: true,
});
`$3
Shapes an object by picking or omitting specified keys, with TypeScript inferring exact key types.`ts
import { shape } from 'q-js-utils/shape';const user = {
id: 1,
name: "Husein",
email: "husein@example.com",
role: "admin",
};
const pick = shape(user, ["id", "name"] as const); // { id: 1; name: "Husein" }
const omit = shape(user, ["id", "name"] as const, true); // { email: "husein@example.com"; role: "admin" }
`$3
Capitalizes the first letter.`ts
import { capitalize } from 'q-js-utils/capitalize';console.log(capitalize("husein")); // Husein
console.log(capitalize("élève")); // "Élève"
console.log(capitalize("mañana")); // "Mañana"
``