everyday-helper
v1.2.3
Published
A lightweight collection of React hooks, utility functions, and helpers for modern React development
Downloads
2,498
Maintainers
Readme
everyday-helper
A lightweight collection of React hooks, utility functions, and helpers that'll make your development workflow smoother. This package brings together commonly needed utilities so you don't have to reinvent the wheel every time you start a new project.
Contributing
Feel free to open issues or submit pull requests if you find bugs or have suggestions for improvements!
What's Inside?
This package is organized into four main sections:
- Hooks - React hooks for common patterns
- Utils - Pure utility functions for data manipulation
- Lib - Helper libraries for specific tasks
- Constants - Predefined constant values
Library Helpers
FormDataBuilder
Build FormData objects with ease.
import { createFormData } from 'everyday-helper/lib';
const formData = createFormData()
.append('name', 'John')
.append('email', '[email protected]')
.appendFile('avatar', avatarFile)
.appendJSON('metadata', { age: 30 })
.appendArray('tags', [{ id: 1, name: 'tech' }])
.build();
// With options
const formData2 = createFormData({
skipNull: true,
skipUndefined: true,
skipEmptyStrings: true,
})
.appendFields({ name: 'John', age: 30 })
.build();Storage Lib
Type-safe localStorage and sessionStorage wrappers.
import { local, session } from 'everyday-helper/lib';
// Local storage
local.set('user', { id: 1, name: 'John' });
const user = local.get<User>('user');
const userOrDefault = local.getOr('user', { id: 0, name: 'Guest' });
local.remove('user');
local.clear();
// Session storage
session.set('temp_data', { foo: 'bar' });
const data = session.get('temp_data');
session.has('temp_data'); // true
const allKeys = session.keys();cn - Class Name Utility
Conditionally combine class names.
import cn from 'everyday-helper/lib';
function Button({ isActive, isPrimary, className }) {
return (
<button
className={cn(
'btn',
{
active: isActive,
'btn-primary': isPrimary,
},
className,
)}
>
Click me
</button>
);
}CookieManager
Simple cookie management.
import { CookieManager } from 'everyday-helper/lib';
// Set cookie
CookieManager.set('user_token', 'abc123', {
domain: '.example.com',
path: '/',
expires: 7, // 7 days
secure: true,
sameSite: 'Strict',
});
// Get cookie
const token = CookieManager.get('user_token');
// Remove cookie
CookieManager.remove('user_token');lazyLoad
Lazy load React components with retry logic.
import lazyLoad from 'everyday-helper/lib';
const components = lazyLoad(
{
HomePage: () => import('./pages/Home'),
AboutPage: () => import('./pages/About'),
Dashboard: () => import('./pages/Dashboard'),
},
{
retries: 2,
delayMs: 250,
},
);
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<components.HomePage />} />
<Route path="/about" element={<components.AboutPage />} />
</Routes>
</Suspense>
);
}React Hooks
usePortal
Create a portal container dynamically for modals, tooltips, etc.
import { usePortal } from 'everyday-helper/hooks';
import { createPortal } from 'react-dom';
function Modal({ children }) {
const portalRef = usePortal({ id: 'modal-root' });
if (!portalRef.current) return null;
return createPortal(children, portalRef.current);
}useAppLocation
A wrapper around React Router's useLocation that provides convenient methods for working with the current route.
import { useAppLocation } from 'everyday-helper/hooks';
function NavBar() {
const { pathname, isActive, includes, startsWith } = useAppLocation();
return (
<nav>
<a className={isActive('/home') ? 'active' : ''}>Home</a>
<a className={includes('/dashboard') ? 'active' : ''}>Dashboard</a>
<a className={startsWith('/admin') ? 'active' : ''}>Admin</a>
</nav>
);
}Returns:
pathname- Current pathnamehash- URL hashsearch- URL search paramsisActive(path)- Check if path exactly matches current pathnameincludes(segment)- Check if pathname includes a segmentstartsWith(prefix)- Check if pathname starts with a prefix
useDebounce
Debounces a value, useful for search inputs or expensive operations.
import { useDebounce } from 'everyday-helper/hooks';
function SearchBar() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
// This only runs 500ms after user stops typing
if (debouncedSearch) {
fetchSearchResults(debouncedSearch);
}
}, [debouncedSearch]);
return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />;
}Parameters:
value- The value to debouncedelay- Delay in milliseconds (default: 500)
useDownloadFile
Provides a simple way to download images from URLs.
import { useDownloadFile } from 'everyday-helper/hooks';
function ImageGallery() {
const { downloadImage } = useDownloadFile();
return (
<button onClick={() => downloadImage('/api/image.jpg', 'my-image')}>Download Image</button>
);
}Methods:
downloadImage(src, filename)- Downloads image as PNG file
useEscapeKey
Execute a callback when the Escape key is pressed.
import { useEscapeKey } from 'everyday-helper/hooks';
function Modal({ onClose }) {
useEscapeKey({
onEscape: onClose,
enabled: true,
preventDefault: true,
});
return <div className="modal">...</div>;
}Props:
onEscape- Callback function to executeenabled- Whether the hook is active (default: true)preventDefault- Prevent default behavior (default: false)
useEventListener
Attach event listeners to window or document with automatic cleanup.
import { useEventListener } from 'everyday-helper/hooks';
function Component() {
useEventListener('scroll', () => {
console.log('User scrolled!');
});
useEventListener('click', handleClick, document);
return <div>...</div>;
}Parameters:
event- Event namehandler- Event handler functionelement- Target element (default: window)
useOnlineStatus
Track the user's online/offline status in real-time.
import { useOnlineStatus } from 'everyday-helper/hooks';
function ConnectionStatus() {
const isOnline = useOnlineStatus();
return (
<div className={isOnline ? 'online' : 'offline'}>
{isOnline ? 'Connected' : 'No internet connection'}
</div>
);
}useOutsideClick
Detect clicks outside a specific element.
import { useOutsideClick } from 'everyday-helper/hooks';
import { useRef } from 'react';
function Dropdown() {
const dropdownRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
useOutsideClick(dropdownRef, () => setIsOpen(false));
return <div ref={dropdownRef}>{/* Clicking outside closes the dropdown */}</div>;
}Options:
id- Target element ID (if not provided, appends to body)
usePrevious
Keep track of a value from the previous render.
import { usePrevious } from 'everyday-helper/hooks';
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
</div>
);
}usePrint
Print images with customizable styling in a new window or iframe.
import { usePrint } from 'everyday-helper/hooks';
function ImageViewer({ imageUrl }) {
const { printInBlank, printInCurrent } = usePrint();
return (
<>
<button onClick={() => printInBlank(imageUrl, { padding: '20px' })}>
Print in New Window
</button>
<button onClick={() => printInCurrent(imageUrl, { fit: 'contain' })}>
Print in Current Page
</button>
</>
);
}Methods:
printInBlank(src, style?)- Opens print dialog in new windowprintInCurrent(src, style?)- Prints using hidden iframe
Style Options:
padding- Padding around image (default: '20px')fit- Object-fit value (default: 'contain')maxWidth- Maximum width (default: '100%')maxHeight- Maximum height (default: '100%')background- Background color (default: '#fff')printDelay- Delay before printing (default: 150ms)cleanupDelay- Cleanup delay for iframe (default: 10000ms)
useResizeListener
Listen for window resize events with optional activation control.
import { useResizeListener } from 'everyday-helper/hooks';
function ResponsiveComponent() {
const [width, setWidth] = useState(window.innerWidth);
useResizeListener(() => {
setWidth(window.innerWidth);
}, true);
return <div>Window width: {width}px</div>;
}useScrollLock
Lock/unlock body scroll, perfect for modals and overlays.
import { useScrollLock } from 'everyday-helper/hooks';
function Modal({ isOpen }) {
useScrollLock({ isLocked: isOpen });
return isOpen ? <div className="modal">...</div> : null;
}useToggle
Simple toggle state management with helpful methods.
import { useToggle } from 'everyday-helper/hooks';
function Accordion() {
const { isActive, toggle, onOpen, onClose } = useToggle();
return (
<div>
<button onClick={toggle}>Toggle</button>
<button onClick={onOpen}>Open</button>
<button onClick={onClose}>Close</button>
{isActive && <div>Accordion content</div>}
</div>
);
}Returns:
isActive- Current state (boolean)toggle()- Toggle the stateonOpen()- Set to trueonClose()- Set to false
Utility Functions
Array Utils
Working with arrays made easier.
import {
uniqueBy,
groupBy,
chunk,
first,
last,
shuffle,
sortBy,
partition,
} from 'everyday-helper/utils';
// Remove duplicates by key
const users = uniqueBy(allUsers, 'id');
// Group items by property
const usersByRole = groupBy(users, 'role');
// { admin: [...], user: [...] }
// Split into chunks
const pages = chunk(items, 10); // [[1-10], [11-20], ...]
// Get first/last elements
const firstUser = first(users);
const lastUser = last(users);
// Shuffle array randomly
const randomOrder = shuffle(items);
// Sort by key or function
const sorted = sortBy(users, 'name', 'asc');
const custom = sortBy(users, (u) => u.age, 'desc');
// Partition based on condition
const [active, inactive] = partition(users, (u) => u.isActive);Available functions:
uniqueBy(arr, key)- Unique elements by propertygroupBy(arr, key)- Group by propertychunk(arr, size)- Split into chunkscheckArrEquality(arr1, arr2)- Deep equality checkreverseArr(arr)- Reverse arrayreject(arr, predicate)- Opposite of filtercount(arr, predicate?)- Count matching elementspushIf(arr, element, condition)- Conditional pushfirst(arr)- First elementlast(arr)- Last elementunique(arr)- Unique primitivesflatten(arr)- Flatten one levelflattenDeep(arr)- Flatten all levelscompactArr(arr)- Remove falsy valuessample(arr)- Random elementsampleSize(arr, n)- N random elementsshuffle(arr)- Randomize orderdifference(arr1, arr2)- Elements in arr1 not in arr2intersection(arr1, arr2)- Common elementsunion(arr1, arr2)- All unique elementswithout(arr, ...values)- Remove specific valueszip(...arrays)- Zip arrays into tuplesfromPairs(pairs)- Create object from pairssum(arr)- Sum of numbersaverage(arr)- Average of numbersmin(arr)- Minimum valuemax(arr)- Maximum valuepartition(arr, predicate)- Split by conditionsortBy(arr, keyOrFn, order)- Sort by key/function
String Utils
Comprehensive string manipulation utilities.
import {
capitalize,
truncate,
slugify,
toCamelCase,
toPascalCase,
toSnakeCase,
toKebabCase,
} from 'everyday-helper/utils';
// Case transformations
const camel = toCamelCase('hello-world'); // 'helloWorld'
const pascal = toPascalCase('hello-world'); // 'HelloWorld'
const snake = toSnakeCase('HelloWorld'); // 'hello_world'
const kebab = toKebabCase('HelloWorld'); // 'hello-world'
// Text formatting
const title = capitalize('hello'); // 'Hello'
const short = truncate('Long text here', 10); // 'Long te...'
const url = slugify('Hello World!'); // 'hello-world'
// String checks
const similar = isStringSimilar('dashbord', 'dashboard', 2); // true
const hasMatch = includesIgnoreCase('Hello World', 'WORLD'); // true
const isEqual = eqIgnoreCase('Hello', 'hello'); // trueAvailable functions:
isStringSimilar(input, target, maxDiff)- Fuzzy string matchingconcatIf(s1, s2, condition)- Conditional concatenationisString(v)- Type checkeqIgnoreCase(s1, s2)- Case-insensitive equalityincludesIgnoreCase(s1, s2)- Case-insensitive includesaddAsteriskIf(s, condition)- Conditional asteriskcompactStr(s)- Remove all whitespacetoUpperSnakeCase(str)- UPPER_SNAKE_CASEtoSnakeCase(str)- snake_casetoCamelCase(str)- camelCasetoPascalCase(str)- PascalCasetoKebabCase(str)- kebab-casecapitalize(str)- Capitalize first lettercapitalizeWords(str)- Capitalize each wordtruncate(str, maxLength, suffix)- Truncate with ellipsisreverse(str)- Reverse stringcountOccurrences(str, substr)- Count substringnormalizeWhitespace(str)- Normalize spacespadStart/padEnd(str, length, char)- Pad stringtrim/trimStart/trimEnd(str, chars)- Trim charactersstartsWith/endsWith(str, search, ignoreCase)- String checksrepeat(str, count)- Repeat stringslugify(str)- URL-friendly string
Object Utils
Powerful object manipulation utilities.
import {
pick,
omit,
get,
set,
merge,
deepMerge,
cleanObject,
mapValues,
} from 'everyday-helper/utils';
const user = {
id: 1,
name: 'John',
email: '[email protected]',
role: 'admin',
metadata: { age: 30 },
};
// Pick specific keys
const basicInfo = pick(user, ['name', 'email']);
// { name: 'John', email: '[email protected]' }
// Omit keys
const withoutEmail = omit(user, ['email']);
// Get nested values safely
const age = get(user, 'metadata.age', 0); // 30
// Set nested values
set(user, 'metadata.city', 'NYC');
// Merge objects
const updated = merge(user, { status: 'active' });
const deepMerged = deepMerge(user, { metadata: { location: 'US' } });
// Clean empty values
const cleaned = cleanObject({ name: 'John', email: '', age: null });
// { name: 'John' }
// Transform values
const uppercased = mapValues(user, (val) => (typeof val === 'string' ? val.toUpperCase() : val));Available functions:
pick(obj, keys)- Select specific keysomit(obj, keys)- Remove specific keysmerge(target, source)- Shallow mergecleanObject(obj)- Remove null/undefined/emptyareAllValuesComplete(obj)- Check if all values are filledisObject(o)- Type checkkeys(o)- Get keys (typed)values(o)- Get values (typed)entries(o)- Get entries (typed)deepClone(obj)- Deep clone via JSONdeepMerge(target, source)- Recursive mergeisEqual(obj1, obj2)- Deep equalityget(obj, path, defaultValue)- Safe nested getset(obj, path, value)- Safe nested sethas(obj, path)- Check nested propertyinvert(obj)- Swap keys and valuesmapValues(obj, fn)- Transform valuesmapKeys(obj, fn)- Transform keysdeepFreeze(obj)- Deep freezefilterObject(obj, predicate)- Filter propertiesunflatten(obj)- Convert dot-notation to nested
Date Utils
Comprehensive date utilities powered by native JS.
import {
formatDate,
formatRelativeTime,
isToday,
isYesterday,
isFuture,
addToDate,
getDateDifference,
} from 'everyday-helper/utils';
import { DateFormats } from 'everyday-helper/constants';
// Format dates
const formatted = formatDate(new Date(), DateFormats.DD_MM_YYYY_WITH_DOT);
// '02.12.2025'
const relative = formatRelativeTime(new Date('2025-11-30'));
// '2 days ago'
// Date checks
if (isToday(someDate)) {
console.log('Date is today!');
}
if (isYesterday(someDate)) {
console.log('Date was yesterday');
}
// Date manipulation
const nextWeek = addToDate(new Date(), 7, 'day');
const lastMonth = subtractFromDate(new Date(), 1, 'month');
// Date comparison
const daysDiff = getDateDifference(date1, date2, 'day');
const age = getAge('1990-01-01'); // Age in yearsAvailable functions:
formatDate(date, format)- Format dateformatRelativeTime(date, baseDate?)- Relative time stringisValidDate(date)- Validate dateisPast/isFuture(date)- Check if past/futureisToday/isYesterday/isTomorrow(date)- Day checksisSameDay(date1, date2)- Compare daysisBetweenDates(date, start, end)- Range checkgetDateDifference(date1, date2, unit)- Calculate differenceaddToDate/subtractFromDate(date, amount, unit)- Manipulate datesstartOf/endOf(date, unit)- Get start/end of periodformatDateRange(start, end, format)- Format rangegetAge(birthdate)- Calculate ageparseDate(dateString, format)- Parse with formattoISOString(date)- Convert to ISO stringtoUnixTimestamp(date)- Convert to Unix timestampnow()- Current date/timecompareDates(date1, date2)- Compare dates
Function Utils
Higher-order functions and function utilities.
import { debounce, throttle, memoize, once, retry } from 'everyday-helper/utils';
// Debounce function calls
const debouncedSearch = debounce((query) => {
fetchResults(query);
}, 500);
// Throttle function calls
const throttledScroll = throttle(() => {
updateScrollPosition();
}, 100);
// Memoize expensive calculations
const expensiveCalc = memoize((a, b) => {
// Complex calculation
return result;
});
// Run function only once
const initialize = once(() => {
console.log('Initialized!');
});
// Retry failed async operations
const fetchData = retry(
async () => {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Failed');
return response.json();
},
3,
1000,
); // 3 retries, 1s delayAvailable functions:
noop()- No-op functioncompose(...fns)- Function compositiondebounce(fn, wait)- Debounce with cancelthrottle(fn, wait)- Throttle with cancelmemoize(fn, resolver?)- Memoizationonce(fn)- Call oncedelay(fn, wait, ...args)- Delayed executionretry(fn, retries, delayMs)- Retry async functioncurry(fn)- Curry functionflip(fn)- Flip argumentspartial(fn, ...partials)- Partial applicationnegate(predicate)- Negate predicatesafeCall(fn, ...args)- Safe function calltryCatch(fn)- Sync try-catch wrappertryCatchAsync(fn)- Async try-catch wrapperrateLimit(fn, minDelay)- Rate limitingidentity(value)- Identity functionconstant(value)- Constant functionfirstSeveral(arr, n)- First n elementslastSeveral(arr, n)- Last n elements
Common Utils
General-purpose utility functions.
import { isEmpty, isNotEmpty, isNulOrUndefined, getImageUrl } from 'everyday-helper/utils';
// Check empty values
if (isEmpty(value)) {
// Handles null, undefined, '', [], {}
}
if (isNotEmpty(value)) {
// Value is not empty
}
// Null/undefined checks
if (isNulOrUndefined(value)) {
// value is null or undefined
}
// Generate image URLs
const imageUrl = getImageUrl(fileId, 'https://api.example.com');
// 'https://api.example.com/fs/v1/files/123/download'Convert Utils
File and data conversion utilities.
import {
convertFileToBase64,
convertBase64ToFile,
extractBase64FromDataUrl,
fileToArrayBuffer,
} from 'everyday-helper/utils';
// File to Base64
const base64 = await convertFileToBase64(file);
// 'data:image/png;base64,iVBORw0KG...'
// Base64 to File
const file = convertBase64ToFile(base64String, 'image.png', 'image/png');
// Extract Base64 from data URL
const pureBase64 = extractBase64FromDataUrl(dataUrl);
// File to ArrayBuffer
const buffer = await fileToArrayBuffer(file);API Utils
API and HTTP-related utilities.
import { getEndpoint, generateQuery, isLoggedIn } from 'everyday-helper/utils';
// Get role-based endpoints
const endpoint = getEndpoint('admin', 'users', sharedFeatures);
// Generate query strings
const query = generateQuery({
page: 1,
search: 'john',
filters: ['active', 'verified'],
empty: '',
});
// 'page=1&search=john&filters=active&filters=verified'
// Check login status
if (isLoggedIn('access_token')) {
// User is logged in
}Phone Utils
Azerbaijan phone number utilities.
import {
hasAzerbaijanCountryCode,
withAzerbaijanCountryCode,
normalizePhone,
} from 'everyday-helper/utils';
// Check for country code
const hascode = hasAzerbaijanCountryCode('0501234567'); // false
// Add country code
const phone = withAzerbaijanCountryCode('501234567');
// '+994501234567'
// Normalize phone number
const normalized = normalizePhone('0501234567');
// '+994501234567'Animation Utils
CSS animation helper utilities.
import { animate, fadeIn, slideInUp, bounceIn } from 'everyday-helper/utils';
function AnimatedComponent() {
return (
<>
<div {...fadeIn(0)}>Fades in first</div>
<div {...slideInUp(1)}>Slides in second</div>
<div {...bounceIn(2)}>Bounces in third</div>
{/* Custom animation */}
<div
{...animate({
type: 'fade-in',
order: 3,
delay: 0.5,
className: 'extra-class',
})}
>
Custom timing
</div>
</>
);
}Note: Import animations CSS once in your app:
import 'hh-toolkit/animations.css';Available animations:
fadeIn(order?, className?, style?)slideInUp(order?, className?, style?)slideInDown(order?, className?, style?)slideInLeft(order?, className?, style?)slideInRight(order?, className?, style?)scaleIn(order?, className?, style?)bounceIn(order?, className?, style?)
Constants
DateFormats
Predefined date format strings for use with date utilities.
import { DateFormats } from 'everyday-helper/constants';
DateFormats.MMMM_DD_YYYY; // "MMMM DD, YYYY"
DateFormats.DD_MM_YYYY_WITH_DOT; // "DD.MM.YYYY"
DateFormats.DD_MM_YYYY_WITH_SLASH; // "DD/MM/YYYY"
DateFormats.YYYY_MM_DD_WITH_HYPEN; // "YYYY-MM-DD"
DateFormats.DD_MM_YYYY_WITH_HYPHEN; // "DD-MM-YYYY"
DateFormats.DD_MM_YYYY_HH_mm; // "DD/MM/YYYY HH:mm"
DateFormats.DD_MMM_YYYY_WITH_SPACE; // "DD MMM YYYY"EventTypes
Comprehensive enum of DOM event types.
import { EventTypes } from 'everyday-helper/constants';
// Mouse events
EventTypes.CLICK;
EventTypes.MOUSE_MOVE;
EventTypes.MOUSE_ENTER;
// Keyboard events
EventTypes.KEY_DOWN;
EventTypes.ESCAPE;
EventTypes.ARROW_LEFT;
// Form events
EventTypes.SUBMIT;
EventTypes.CHANGE;
EventTypes.FOCUS;
// Window events
EventTypes.RESIZE;
EventTypes.SCROLL;
EventTypes.LOAD;
// And many more...SortOrders
Sort order constants.
import { SortOrders } from 'everyday-helper/constants';
SortOrders.ASC; // 'asc'
SortOrders.DESC; // 'desc'Import Paths
You can import from specific paths for better tree-shaking:
// Import all from main entry
import { useDebounce, formatDate, cn } from 'everyday-helper';
// Or import from specific modules
import { useDebounce } from 'everyday-helper/hooks';
import { formatDate } from 'everyday-helper/utils';
import { cn } from 'everyday-helper/lib';
import { DateFormats } from 'everyday-helper/constants';TypeScript Support
This package is written in TypeScript and includes full type definitions. You'll get autocomplete and type checking out of the box.
import { formatDate, pick } from 'everyday-helper';
// Types are inferred automatically
const date = formatDate(new Date()); // string | undefined
const user = pick({ name: 'John', age: 30 }, ['name']); // { name: string }Examples
Building a Search Component
import { useDebounce } from 'everyday-helper/hooks';
import { isEmpty } from 'everyday-helper/utils';
import { useState, useEffect } from 'react';
function SearchBar() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (isEmpty(debouncedQuery)) {
setResults([]);
return;
}
fetch(`/api/search?q=${debouncedQuery}`)
.then((res) => res.json())
.then(setResults);
}, [debouncedQuery]);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search..." />
<ul>
{results.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}Modal with Outside Click & Escape Key
import { useOutsideClick, useEscapeKey, useScrollLock } from 'everyday-helper/hooks';
import { useRef } from 'react';
function Modal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
useScrollLock({ isLocked: isOpen });
useOutsideClick(modalRef, onClose);
useEscapeKey({ onEscape: onClose, enabled: isOpen });
if (!isOpen) return null;
return (
<div className="modal-overlay">
<div ref={modalRef} className="modal-content">
{children}
</div>
</div>
);
}Data Table with Sorting
import { sortBy, groupBy } from 'everyday-helper/utils';
import { SortOrders } from 'everyday-helper/constants';
import { useState } from 'react';
function DataTable({ data }) {
const [sortKey, setSortKey] = useState('name');
const [sortOrder, setSortOrder] = useState(SortOrders.ASC);
const sortedData = sortBy(data, sortKey, sortOrder);
const groupedByRole = groupBy(sortedData, 'role');
return (
<table>
<thead>
<tr>
<th onClick={() => setSortKey('name')}>Name</th>
<th onClick={() => setSortKey('age')}>Age</th>
<th onClick={() => setSortKey('role')}>Role</th>
</tr>
</thead>
<tbody>
{sortedData.map((row) => (
<tr key={row.id}>
<td>{row.name}</td>
<td>{row.age}</td>
<td>{row.role}</td>
</tr>
))}
</tbody>
</table>
);
}