move addtional files to stricter linting

This commit is contained in:
Nate Kelley 2025-05-28 15:48:26 -06:00
parent 3558a4366a
commit c7f8dbcfb7
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
24 changed files with 98 additions and 60 deletions

View File

@ -22,7 +22,8 @@
"enabled": true,
"ignore": [
"**/*.test.*",
"**/*.spec.*"
"**/*.spec.*",
"**/*.stories.tsx"
],
"rules": {
"recommended": true,

View File

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react';
import { useAsyncEffect } from './useAsyncEffect';
describe('useAsyncEffect', () => {

View File

@ -2,7 +2,7 @@
import { useEffect } from 'react';
type AsyncEffect = () => Promise<void | (() => void)>;
type AsyncEffect = () => Promise<undefined | (() => void)>;
type DependencyList = ReadonlyArray<unknown>;
/**
@ -15,7 +15,7 @@ type DependencyList = ReadonlyArray<unknown>;
export const useAsyncEffect = (effect: AsyncEffect, deps?: DependencyList) => {
useEffect(() => {
let mounted = true;
let cleanup: void | (() => void);
let cleanup: undefined | (() => void);
const runEffect = async () => {
try {

View File

@ -1,8 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import { useRef, useState, useCallback, useEffect } from 'react';
import { useAutoScroll } from './useAutoScroll';
import { ScrollArea } from '@/components/ui/scroll-area';
import { faker } from '@faker-js/faker';
import type { Meta, StoryObj } from '@storybook/react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useAutoScroll } from './useAutoScroll';
interface Message {
id: number;
@ -63,16 +63,19 @@ const AutoScrollDemo = () => {
<div className="flex w-[600px] flex-col gap-4">
<div className="flex gap-2">
<button
type="button"
onClick={addMessage}
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
Add Message
</button>
<button
type="button"
onClick={addManyMessages}
className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600">
Add 10 Messages
</button>
<button
type="button"
onClick={toggleAutoAdd}
className={`rounded px-4 py-2 text-white ${
isAutoAddEnabled
@ -82,6 +85,7 @@ const AutoScrollDemo = () => {
Auto Add {isAutoAddEnabled ? 'ON' : 'OFF'}
</button>
<button
type="button"
onClick={() => setEnabled(!enabled)}
className={`rounded px-4 py-2 text-white ${
enabled ? 'bg-green-500 hover:bg-green-600' : 'bg-red-500 hover:bg-red-600'
@ -94,6 +98,7 @@ const AutoScrollDemo = () => {
<div className="flex items-center gap-2">
<span className="text-sm">Auto-scroll:</span>
<button
type="button"
onClick={isAutoScrollEnabled ? disableAutoScroll : enableAutoScroll}
className={`rounded px-3 py-1 text-white ${
isAutoScrollEnabled ? 'bg-green-500' : 'bg-red-500'
@ -103,21 +108,25 @@ const AutoScrollDemo = () => {
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => scrollToTop()}
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
Scroll to Top (smooth)
</button>
<button
type="button"
onClick={() => scrollToTop()}
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
Scroll to Top (instant)
</button>
<button
type="button"
onClick={() => scrollToBottom()}
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
Scroll to Bottom (smooth)
</button>
<button
type="button"
onClick={() => scrollToBottom()}
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
Scroll to Bottom (instant)
@ -178,7 +187,7 @@ const Lorem = {
{ length: wordCount },
() => Lorem.words[Math.floor(Math.random() * Lorem.words.length)]
);
sentences.push(words.join(' ') + '.');
sentences.push(`${words.join(' ')}.`);
}
return sentences.join(' ');
}
@ -201,7 +210,7 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
render: () => {
const generateCard = (index: number) => ({
id: index,
title: faker.company.name() + ' ' + index,
title: `${faker.company.name()} ${index}`,
color: faker.color.rgb(),
sentences: faker.lorem.sentences(2)
});
@ -253,11 +262,13 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
<h3 className="text-lg font-semibold">Scrollable Grid Layout</h3>
<div className="flex gap-2">
<button
type="button"
onClick={addCard}
className="rounded bg-blue-500 px-3 py-1 text-sm text-white hover:bg-blue-600">
Add Card
</button>
<button
type="button"
onClick={toggleAutoAdd}
className={`rounded px-3 py-1 text-sm text-white ${
isAutoAddEnabled
@ -267,6 +278,7 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
Auto Add {isAutoAddEnabled ? 'ON' : 'OFF'}
</button>
<button
type="button"
onClick={isAutoScrollEnabled ? disableAutoScroll : enableAutoScroll}
className={`rounded px-3 py-1 text-sm text-white ${
isAutoScrollEnabled
@ -276,11 +288,13 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
Auto-scroll {isAutoScrollEnabled ? 'ON' : 'OFF'}
</button>
<button
type="button"
onClick={() => scrollToTop()}
className="rounded bg-gray-500 px-3 py-1 text-sm text-white hover:bg-gray-600">
To Top
</button>
<button
type="button"
onClick={() => scrollToBottom()}
className="rounded bg-gray-500 px-3 py-1 text-sm text-white hover:bg-gray-600">
To Bottom
@ -322,7 +336,7 @@ export const RapidTextAppend: Story = {
const addWord = useCallback(() => {
const randomWord = faker.word.words(2);
setText((old) => old + ' ' + randomWord);
setText((old) => `${old} ${randomWord}`);
}, []);
const toggleRunning = useCallback(() => {
@ -355,6 +369,7 @@ export const RapidTextAppend: Story = {
<h3 className="text-lg font-semibold">Rapid Text Append</h3>
<div className="flex gap-2">
<button
type="button"
onClick={toggleRunning}
className={`rounded px-3 py-1 text-sm text-white ${
isRunning ? 'bg-red-500 hover:bg-red-600' : 'bg-green-500 hover:bg-green-600'
@ -362,6 +377,7 @@ export const RapidTextAppend: Story = {
{isRunning ? 'Stop' : 'Start'} Adding Words
</button>
<button
type="button"
onClick={isAutoScrollEnabled ? disableAutoScroll : enableAutoScroll}
className={`rounded px-3 py-1 text-sm text-white ${
isAutoScrollEnabled

View File

@ -1,10 +1,10 @@
'use client';
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import useLatest from './useLatest';
import { isDev } from '@/config';
import debounce from 'lodash/debounce';
import isFunction from 'lodash/isFunction';
import { isDev } from '@/config';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useLatest from './useLatest';
import { useUnmount } from './useUnmount';
interface DebounceOptions {
@ -14,6 +14,7 @@ interface DebounceOptions {
trailing?: boolean;
}
// biome-ignore lint/suspicious/noExplicitAny: Required for generic function types
type noop = (...args: any[]) => any;
export function useDebounceFn<T extends noop>(fn: T, options?: DebounceOptions) {

View File

@ -1,9 +1,9 @@
'use client';
import isEqual from 'lodash/isEqual';
import { useLayoutEffect, useState, useTransition } from 'react';
import { useDebounceFn } from './useDebounce';
import { useMemoizedFn } from './useMemoizedFn';
import { useLayoutEffect, useState, useTransition } from 'react';
import isEqual from 'lodash/isEqual';
interface UseDebounceSearchProps<T> {
items: T[];

View File

@ -1,9 +1,9 @@
import { type DependencyList, type EffectCallback, useEffect, type useLayoutEffect } from 'react';
import { useRef } from 'react';
import { useUnmount } from './useUnmount';
import { depsAreSame } from '@/lib/depAreSame';
import type { BasicTarget } from '@/lib/domTarget';
import { getTargetElement } from '@/lib/domTarget';
import { type DependencyList, type EffectCallback, useEffect, type useLayoutEffect } from 'react';
import { useRef } from 'react';
import { useUnmount } from './useUnmount';
const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayoutEffect) => {
/**
@ -15,14 +15,14 @@ const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayo
const useEffectWithTarget = (
effect: EffectCallback,
deps: DependencyList,
target: BasicTarget<any> | BasicTarget<any>[]
target: BasicTarget<Element> | BasicTarget<Element>[]
) => {
const hasInitRef = useRef(false);
const lastElementRef = useRef<(Element | null)[]>([]);
const lastElementRef = useRef<(Element | null | undefined)[]>([]);
const lastDepsRef = useRef<DependencyList>([]);
const unLoadRef = useRef<any>();
const unLoadRef = useRef<ReturnType<EffectCallback>>();
useEffectType(() => {
const targets = Array.isArray(target) ? target : [target];
@ -43,7 +43,9 @@ const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayo
!depsAreSame(lastElementRef.current, els) ||
!depsAreSame(lastDepsRef.current, deps)
) {
unLoadRef.current?.();
if (typeof unLoadRef.current === 'function') {
unLoadRef.current();
}
lastElementRef.current = els;
lastDepsRef.current = deps;
@ -52,7 +54,9 @@ const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayo
});
useUnmount(() => {
unLoadRef.current?.();
if (typeof unLoadRef.current === 'function') {
unLoadRef.current();
}
// for react-refresh
hasInitRef.current = false;
});

View File

@ -24,7 +24,9 @@ export function useInViewport(target: BasicTarget | BasicTarget[], options?: Opt
useEffectWithTarget(
() => {
const targets = Array.isArray(target) ? target : [target];
const els = targets.map((element) => getTargetElement(element)).filter(Boolean);
const els = targets
.map((element) => getTargetElement(element))
.filter((el): el is Element => el != null);
if (!els.length) {
return;
@ -44,7 +46,9 @@ export function useInViewport(target: BasicTarget | BasicTarget[], options?: Opt
}
);
els.forEach((el) => observer.observe(el!));
for (const el of els) {
observer.observe(el);
}
return () => {
observer.disconnect();

View File

@ -1,7 +1,7 @@
'use client';
import { useRef } from 'react';
import isEqual from 'lodash/isEqual';
import { useRef } from 'react';
import { useMemoizedFn } from './useMemoizedFn';
export const useIsChanged = <T = unknown>() => {

View File

@ -2,6 +2,7 @@
import { useMemo, useRef } from 'react';
// biome-ignore lint/suspicious/noExplicitAny: Required for generic function types
type noop = (this: any, ...args: any[]) => any;
type PickFunction<T extends noop> = (

View File

@ -19,21 +19,21 @@ type ReactRef<T> = React.RefCallback<T> | React.MutableRefObject<T> | null;
* });
* ```
*/
export function useMergedRefs<T = any>(refs: ReactRef<T>[]): React.RefCallback<T> {
export function useMergedRefs<T = unknown>(refs: ReactRef<T>[]): React.RefCallback<T> {
return useCallback(
(element: T | null) => {
refs.forEach((ref) => {
if (!ref) return;
for (const ref of refs) {
if (!ref) continue;
// Handle callback refs
if (typeof ref === 'function') {
ref(element);
return;
continue;
}
// Handle object refs
(ref as React.MutableRefObject<T | null>).current = element;
});
}
},
[refs]
);

View File

@ -1,7 +1,7 @@
'use client';
import type React from 'react';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useMemoizedFn } from './useMemoizedFn';
interface MouseState {
@ -76,9 +76,11 @@ export function useMouse(options: UseMouseOptions = {}) {
const element = target?.current ?? document;
// biome-ignore lint/suspicious/noExplicitAny: Element can be Document or HTMLElement with different event listener signatures
element.addEventListener('mousemove', updateMouseState as any);
return () => {
// biome-ignore lint/suspicious/noExplicitAny: Element can be Document or HTMLElement with different event listener signatures
element.removeEventListener('mousemove', updateMouseState as any);
// Clear any pending timers

View File

@ -1,6 +1,6 @@
'use client';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
interface NetworkState {
online: boolean;
@ -20,6 +20,7 @@ export function useNetwork(): NetworkState {
useEffect(() => {
function updateNetworkInfo() {
if (typeof navigator !== 'undefined' && 'connection' in navigator) {
// biome-ignore lint/suspicious/noExplicitAny: navigator.connection is not fully standardized
const connection = (navigator as any).connection;
setNetwork({
online: navigator.onLine,
@ -46,6 +47,7 @@ export function useNetwork(): NetworkState {
window.addEventListener('offline', updateNetworkInfo);
if (typeof navigator !== 'undefined' && 'connection' in navigator) {
// biome-ignore lint/suspicious/noExplicitAny: navigator.connection is not fully standardized
(navigator as any).connection?.addEventListener('change', updateNetworkInfo);
}
@ -54,6 +56,7 @@ export function useNetwork(): NetworkState {
window.removeEventListener('online', updateNetworkInfo);
window.removeEventListener('offline', updateNetworkInfo);
if (typeof navigator !== 'undefined' && 'connection' in navigator) {
// biome-ignore lint/suspicious/noExplicitAny: navigator.connection is not fully standardized
(navigator as any).connection?.removeEventListener('change', updateNetworkInfo);
}
};

View File

@ -1,6 +1,6 @@
'use client';
import { useState, useEffect, type RefObject } from 'react';
import { type RefObject, useEffect, useState } from 'react';
interface ScrollState {
left: number;

View File

@ -1,6 +1,6 @@
'use client';
import { useEffect, useRef, useState, useCallback } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useMemoizedFn } from './useMemoizedFn';
/**
@ -34,11 +34,10 @@ export function useSetInterval(callback: () => void, delay: number | null) {
setIsActive(false);
}
};
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
setIsActive(false);
}
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
setIsActive(false);
}
}, [delay, savedCallback]);

View File

@ -39,9 +39,8 @@ export function useSize(
if (debounceDelay > 0) {
const timeoutId = setTimeout(updateSize, debounceDelay);
return () => clearTimeout(timeoutId);
} else {
updateSize();
}
updateSize();
});
useEffect(() => {

View File

@ -9,6 +9,7 @@ interface ThrottleOptions {
trailing?: boolean;
}
// biome-ignore lint/suspicious/noExplicitAny: Required for generic function types
export function useThrottleFn<T extends (...args: any[]) => any>(
fn: T,
options: ThrottleOptions = {}

View File

@ -1,6 +1,6 @@
'use client';
import { useEffect, useRef } from 'react';
import { type DependencyList, useEffect, useRef } from 'react';
/**
* A hook that executes a function on the second update and subsequent updates.
@ -8,7 +8,7 @@ import { useEffect, useRef } from 'react';
* @param effect The effect function to run
* @param deps The dependencies array
*/
export const useUpdateEffect = (effect: () => void | (() => void), deps?: any[]) => {
export const useUpdateEffect = (effect: () => undefined | (() => void), deps?: DependencyList) => {
const isFirstRender = useRef(true);
useEffect(() => {

View File

@ -1,6 +1,6 @@
'use client';
import { useLayoutEffect, useRef } from 'react';
import { type DependencyList, useLayoutEffect, useRef } from 'react';
/**
* A hook that executes a function on the second update and subsequent updates.
@ -8,7 +8,10 @@ import { useLayoutEffect, useRef } from 'react';
* @param effect The effect function to run
* @param deps The dependencies array
*/
export const useUpdateLayoutEffect = (effect: () => void | (() => void), deps?: any[]) => {
export const useUpdateLayoutEffect = (
effect: () => undefined | (() => void),
deps?: DependencyList
) => {
const isFirstRender = useRef(true);
useLayoutEffect(() => {

View File

@ -56,6 +56,7 @@ async function detectDeviceCapabilities(): Promise<DeviceCapabilities> {
const cores = navigator.hardwareConcurrency || 2;
// Check device memory (if available)
// biome-ignore lint/suspicious/noExplicitAny: navigator.deviceMemory is experimental and not standardized
const memory = (navigator as any).deviceMemory || 4;
// Calculate device tier
@ -65,11 +66,11 @@ async function detectDeviceCapabilities(): Promise<DeviceCapabilities> {
memory >= 4
) {
return DEVICE_TIERS.high;
} else if (performanceScore < 100 && cores >= 2 && memory >= 2) {
return DEVICE_TIERS.medium;
} else {
return DEVICE_TIERS.low;
}
if (performanceScore < 100 && cores >= 2 && memory >= 2) {
return DEVICE_TIERS.medium;
}
return DEVICE_TIERS.low;
} catch (error) {
console.warn('Error detecting device capabilities:', error);
return DEVICE_TIERS.medium; // Fallback to medium tier

View File

@ -1,10 +1,10 @@
import { isDev } from '@/config';
import type { BusterSocketResponseRoute } from '@/api/buster_socket';
import type {
BusterSocketResponseBase,
BusterSocketResponseMessage
} from '@/api/buster_socket/base_interfaces';
import { ChatsResponses } from '@/api/buster_socket/chats';
import type { BusterSocketResponseRoute } from '@/api/buster_socket';
import { isDev } from '@/config';
export const createBusterResponse = (
message: BusterSocketResponseMessage

View File

@ -1,17 +1,18 @@
'use client';
import type { BusterSocketResponseBase } from '@/api/buster_socket/base_interfaces';
import type { SupabaseContextReturnType } from '@/context/Supabase';
import { useMemoizedFn, useMount, useNetwork, useThrottleFn, useWindowFocus } from '@/hooks';
import { useEffect, useRef, useState } from 'react';
import { ReadyState } from './config';
import type { BusterSocketResponseBase } from '@/api/buster_socket/base_interfaces';
import { createBusterResponse } from './helpers';
import { type DeviceCapabilities, getDeviceCapabilities } from './deviceCapabilities';
import type { SupabaseContextReturnType } from '@/context/Supabase';
import { createBusterResponse } from './helpers';
type WebSocketHookProps = {
canConnect: boolean;
url: string;
checkTokenValidity: SupabaseContextReturnType['checkTokenValidity'];
// biome-ignore lint/suspicious/noExplicitAny: Generic message data type for WebSocket responses
onMessage: (data: BusterSocketResponseBase<string, any>) => void; // Required prop for handling messages
};
@ -26,6 +27,7 @@ const useWebSocket = ({ url, checkTokenValidity, canConnect, onMessage }: WebSoc
const { online } = useNetwork();
const messageQueue = useRef<QueuedMessage[]>([]); // Updated queue type
const processing = useRef<boolean>(false); // Flag to indicate if processing is ongoing
// biome-ignore lint/suspicious/noExplicitAny: Generic message queue for WebSocket data
const sendQueue = useRef<Record<string, any>[]>([]); // Queue to store messages to be sent
const ws = useRef<WebSocket | null>(null);
const capabilities = useRef<DeviceCapabilities | null>(null);
@ -123,6 +125,7 @@ const useWebSocket = ({ url, checkTokenValidity, canConnect, onMessage }: WebSoc
}
});
// biome-ignore lint/suspicious/noExplicitAny: Generic data type for WebSocket messages
const sendJSONMessage = useMemoizedFn(async (data: Record<string, any>, isFromQueue = false) => {
await checkTokenValidity(); //needed! This will refresh the token if it is expired. All other messages will be queued until the token is refreshed.
if (ws.current?.readyState === ReadyState.Closed) {
@ -157,7 +160,7 @@ const useWebSocket = ({ url, checkTokenValidity, canConnect, onMessage }: WebSoc
.then(({ access_token, isTokenValid }) => {
if (!isTokenValid) return;
// If fetch succeeds, establish WebSocket connection
const socketURLWithAuth = url + `?authentication=${access_token}`;
const socketURLWithAuth = `${url}?authentication=${access_token}`;
ws.current = new WebSocket(socketURLWithAuth);
setupWebSocketHandlers();
})

View File

@ -2,7 +2,7 @@
import { useEffect, useRef } from 'react';
export type IProps = Record<string, any>;
export type IProps = Record<string, unknown>;
export function useWhyDidYouUpdate(componentName: string, props: IProps) {
const prevProps = useRef<IProps>({});
@ -12,14 +12,14 @@ export function useWhyDidYouUpdate(componentName: string, props: IProps) {
const allKeys = Object.keys({ ...prevProps.current, ...props });
const changedProps: IProps = {};
allKeys.forEach((key) => {
for (const key of allKeys) {
if (!Object.is(prevProps.current[key], props[key])) {
changedProps[key] = {
from: prevProps.current[key],
to: props[key]
};
}
});
}
if (Object.keys(changedProps).length) {
console.log('[why-did-you-update]', componentName, changedProps);

View File

@ -1,6 +1,6 @@
'use client';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
export const useWindowWidth = () => {
const [isMobile, setIsMobile] = useState(false);