Skip to content

Instantly share code, notes, and snippets.

@composite
Last active March 27, 2025 02:30
Show Gist options
  • Save composite/491ae1c057b37f2b7fd4775368cf8eb0 to your computer and use it in GitHub Desktop.
Save composite/491ae1c057b37f2b7fd4775368cf8eb0 to your computer and use it in GitHub Desktop.
AI Generated react hooks
'use client';
import { useState, useCallback, useMemo, Dispatch, SetStateAction } from 'react';
export interface DataArray<R> {
data: R[] & { added: R[]; updated: R[]; removed: R[]; initial: R[] };
length: number;
isMutated: boolean;
isInsert: boolean;
isUpdate: boolean;
isDelete: boolean;
save: () => DataArray<R>;
reset: () => DataArray<R>;
clear: () => DataArray<R>;
add: (...item: R[]) => DataArray<R>;
insert: (index: number, itemOrInitializer: R | (() => R)) => DataArray<R>;
update: (index: number, itemOrUpdater: SetStateAction<R>) => DataArray<R>;
remove: (...index: number[]) => DataArray<R>;
filter: (predicate: (item: R, index: number, array: readonly R[]) => boolean) => DataArray<R>;
batch: (callback: (data: R[]) => R[]) => DataArray<R>;
}
/**
* A hook that provides a data array with additional properties and methods to manage the data array.
* @param initArray The initial array of data
* @returns DataArray<R> with tracking capabilities
*/
export function useDataArray<R>(initArray: R[] = []): DataArray<R> {
// Core state management
const [data, setData] = useState<R[]>(() => [...initArray]);
const [added, setAdded] = useState<R[]>([]);
const [updated, setUpdated] = useState<R[]>([]);
const [removed, setRemoved] = useState<R[]>([]);
const [initial, setInitial] = useState<R[]>(() => [...initArray]);
// Helper to handle negative indices
const normalizeIndex = useCallback(
(index: number): number => {
if (index < 0) return Math.max(0, data.length + index);
return Math.min(index, data.length);
},
[data.length]
);
// Mutation tracking properties
const isInsert = useMemo(() => added.length > 0, [added]);
const isUpdate = useMemo(() => updated.length > 0, [updated]);
const isDelete = useMemo(() => removed.length > 0, [removed]);
const isMutated = useMemo(() => isInsert || isUpdate || isDelete, [isInsert, isUpdate, isDelete]);
// Enhance data array with tracking properties
const enhancedData = useMemo(() => {
const result = [...data] as R[] & { added: R[]; updated: R[]; removed: R[]; initial: R[] };
result.added = added;
result.updated = updated;
result.removed = removed;
result.initial = initial;
return result;
}, [data, added, updated, removed, initial]);
// Create the DataArray instance
const dataArray = useMemo<DataArray<R>>(() => {
return {
// Read-only properties
data: enhancedData,
length: data.length,
isMutated,
isInsert,
isUpdate,
isDelete,
// State management methods
save: () => {
// Update initial state to current data
setInitial([...data]);
// Clear tracking states
setAdded([]);
setUpdated([]);
setRemoved([]);
return dataArray;
},
reset: () => {
setData([...initial]);
setAdded([]);
setUpdated([]);
setRemoved([]);
return dataArray;
},
clear: () => {
setRemoved(prev => [...prev, ...data]);
setData([]);
return dataArray;
},
// Data modification methods
add: (...items: R[]) => {
if (items.length === 0) return dataArray;
setData(prev => [...prev, ...items]);
setAdded(prev => [...prev, ...items]);
return dataArray;
},
insert: (index: number, itemOrInitializer: R | (() => R)) => {
const normalizedIndex = normalizeIndex(index);
let newItem: R;
if (typeof itemOrInitializer === 'function') {
newItem = (itemOrInitializer as () => R)();
} else {
newItem = itemOrInitializer;
}
setData(prev => {
const newData = [...prev];
newData.splice(normalizedIndex, 0, newItem);
return newData;
});
setAdded(prev => [...prev, newItem]);
return dataArray;
},
update: (index: number, itemOrUpdater: SetStateAction<R>) => {
const normalizedIndex = normalizeIndex(index);
if (normalizedIndex >= 0 && normalizedIndex < data.length) {
const currentItem = data[normalizedIndex];
let newItem: R;
if (typeof itemOrUpdater === 'function') {
newItem = (itemOrUpdater as (prevState: R) => R)(currentItem);
} else {
newItem = itemOrUpdater;
}
setData(prev => {
const newData = [...prev];
newData[normalizedIndex] = newItem;
return newData;
});
setUpdated(prev => [...prev, newItem]);
}
return dataArray;
},
filter: (predicate: (item: R, index: number, array: readonly R[]) => boolean) => {
const filteredItems: R[] = [];
const excludedItems: R[] = [];
data.forEach((item, index, array) => {
if (predicate(item, index, array)) {
filteredItems.push(item);
} else {
excludedItems.push(item);
}
});
setData(filteredItems);
if (excludedItems.length > 0) {
setRemoved(prev => [...prev, ...excludedItems]);
}
return dataArray;
},
remove: (...indices: number[]) => {
if (indices.length === 0) return dataArray;
const normalizedIndices = indices
.map(normalizeIndex)
.filter(idx => idx >= 0 && idx < data.length)
.sort((a, b) => b - a);
if (normalizedIndices.length === 0) return dataArray;
const itemsToRemove = normalizedIndices.map(idx => data[idx]);
setData(prev => {
const newData = [...prev];
for (const idx of normalizedIndices) {
newData.splice(idx, 1);
}
return newData;
});
setRemoved(prev => [...prev, ...itemsToRemove]);
return dataArray;
},
batch: (callback: (data: R[]) => R[]) => {
try {
const currentData = [...data];
const newData = callback(currentData);
if (!Array.isArray(newData)) {
console.error('Batch callback must return an array');
return dataArray;
}
setRemoved(prev => [...prev, ...data]);
setAdded(prev => [...prev, ...newData]);
setData(newData);
} catch (error) {
console.error('Error in batch operation:', error);
}
return dataArray;
},
};
}, [data, added, updated, removed, initial, normalizeIndex, enhancedData, isMutated, isInsert, isUpdate, isDelete]);
return dataArray;
}
'use client';
import { useCallback, useEffect, useRef } from 'react';
// Global state for singleton animation frame
interface DurationCallbackState {
startTimestamp: number;
nextTimestamp: number;
callbacks: Array<{
callback: () => void;
deps: unknown[];
once: boolean;
executed: boolean;
}>;
}
// Singleton map to track all durations and their callbacks
const durationMap = new Map<number, DurationCallbackState>();
let animationFrameId: number | null = null;
// Animation frame handler function
function handleAnimationFrame(timestamp: number) {
let shouldContinue = false;
// Check each duration entry
durationMap.forEach((state, duration) => {
// Calculate if it's time to execute callbacks
if (timestamp >= state.nextTimestamp) {
// Calculate next execution time
state.nextTimestamp = timestamp + duration;
// Execute all callbacks for this duration
state.callbacks = state.callbacks.filter((entry) => {
// Skip if it's a one-time callback that's already been executed
if (entry.once && entry.executed) {
return false;
}
// Execute the callback
entry.callback();
// Mark as executed if it's a one-time callback
if (entry.once) {
entry.executed = true;
return false;
}
return true;
});
// Remove this duration entry if there are no more callbacks
if (state.callbacks.length === 0) {
durationMap.delete(duration);
} else {
shouldContinue = true;
}
} else {
shouldContinue = true;
}
});
// Continue the animation frame loop if there are still callbacks
if (shouldContinue) {
animationFrameId = requestAnimationFrame(handleAnimationFrame);
} else {
animationFrameId = null;
}
}
/**
* The effect will fire every `duration` milliseconds.
* @param {number} duration in milliseconds
* @param {(...args: T) => void} effect callback
* @param {T} dependencies array of dependencies; you must pass an array, even if it's empty
* @param {boolean} once if true, fire effects only once
* @returns {number} next firing date as timestamp in milliseconds
* @typeParam T
*/
export function useDurationEffect<T extends unknown[]>(
duration: number,
effect: (...args: T) => void,
dependencies: T,
once: boolean = false
): number {
// Store the callback with a ref to avoid recreation on rerenders
const callbackRef = useRef<(...args: T) => void>(effect);
// Update the callback ref when the effect changes
useEffect(() => {
callbackRef.current = effect;
}, [effect]);
// Memoize the callback wrapper to maintain dependency identity
const callbackWrapper = useCallback(() => {
callbackRef.current(...dependencies);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
// Setup and cleanup effect
useEffect(() => {
const now = performance.now();
// Get or create the state for this duration
if (!durationMap.has(duration)) {
durationMap.set(duration, {
startTimestamp: now,
nextTimestamp: now + duration,
callbacks: [],
});
}
const state = durationMap.get(duration)!;
// Add this callback to the list
const callbackEntry = {
callback: callbackWrapper,
deps: dependencies,
once,
executed: false,
};
state.callbacks.push(callbackEntry);
// Start the animation frame if not already running
if (animationFrameId === null) {
animationFrameId = requestAnimationFrame(handleAnimationFrame);
}
// Cleanup function to remove this callback when the component unmounts
return () => {
if (durationMap.has(duration)) {
const state = durationMap.get(duration)!;
state.callbacks = state.callbacks.filter((entry) => entry.callback !== callbackWrapper);
if (state.callbacks.length === 0) {
durationMap.delete(duration);
// If no more durations, cancel the animation frame
if (durationMap.size === 0 && animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [duration, callbackWrapper, ...dependencies, once]);
// Return the next firing timestamp
return durationMap.has(duration) ? durationMap.get(duration)!.nextTimestamp : performance.now() + duration;
}
'use client';
import { useEffect, useSyncExternalStore } from 'react';
/**
* Tailwind breakpoint switches
*/
const BREAKPOINTS = [
['sm', 'sm:block', 'sm:hidden'],
['md', 'md:block', 'md:hidden'],
['lg', 'lg:block', 'lg:hidden'],
['xl', 'xl:block', 'xl:hidden'],
['2xl', '2xl:block', '2xl:hidden'],
] as const;
type Breakpoint = (typeof BREAKPOINTS)[number][0];
// Singleton to manage DOM elements and state
const BreakpointManager = (() => {
let isInitialized = false;
const subscribers = new Set<() => void>();
/**
* Inspired in https://stackoverflow.com/a/70754537
*/
const initializeBreakpoints = () => {
if (isInitialized || typeof document === 'undefined') return;
try {
if (document.getElementById('tailwind-breakpoints')) {
isInitialized = true;
return;
}
const container = document.createElement('div');
container.id = 'tailwind-breakpoints';
// Generate HTML for each breakpoint
BREAKPOINTS.forEach(([name], index) => {
const div = document.createElement('div');
div.id = `breakpoint-${name}`;
// Add hidden class as base
div.classList.add('hidden', 'w-0', 'h-0');
// Add responsive classes
BREAKPOINTS.forEach(([_, activeClass, inactiveClass], bpIndex) => {
div.classList.add(index === bpIndex ? activeClass : inactiveClass);
});
container.appendChild(div);
});
document.body.appendChild(container);
isInitialized = true;
} catch (error) {
console.error('Error initializing breakpoint elements:', error);
}
};
const getCurrentBreakpoint = (): Breakpoint | undefined => {
if (typeof document === 'undefined') return undefined;
try {
for (const [name] of BREAKPOINTS) {
const element = document.getElementById(`breakpoint-${name}`);
if (element && element.offsetParent !== null) {
return name;
}
}
} catch (error) {
console.error('Error getting current breakpoint:', error);
}
return undefined;
};
const subscribe = (callback: () => void) => {
if (typeof window === 'undefined') return () => {};
subscribers.add(callback);
const handleResize = () => {
subscribers.forEach((cb) => cb());
};
window.addEventListener('resize', handleResize);
return () => {
subscribers.delete(callback);
if (subscribers.size === 0) {
window.removeEventListener('resize', handleResize);
}
};
};
const getSnapshot = () => {
return getCurrentBreakpoint();
};
return {
initializeBreakpoints,
getCurrentBreakpoint,
subscribe,
getSnapshot,
};
})();
/**
* React hook that returns the current Tailwind breakpoint
* @returns The current active breakpoint (sm, md, lg, xl, 2xl) or undefined
*
* @example
* ```tsx
* const breakpoint = useTailwindBreakpoint();
*
* return (
* <div>
* Current breakpoint: {breakpoint ?? 'xs'}
* </div>
* );
* ```
*/
export function useTailwindBreakpoint() {
useEffect(() => {
BreakpointManager.initializeBreakpoints();
}, []);
const breakpoint = useSyncExternalStore(
BreakpointManager.subscribe,
BreakpointManager.getSnapshot,
() => undefined // Server snapshot
);
return breakpoint;
}
export const breakpoints = BREAKPOINTS.map(([key]) => key);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment