Skip to content

Commit

Permalink
WIP: useData hook + linting
Browse files Browse the repository at this point in the history
  • Loading branch information
tgoyer committed Jun 8, 2024
1 parent a360af8 commit 279566b
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"printWidth": 80,
"printWidth": 120,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
Expand Down
10 changes: 5 additions & 5 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { useData } from './useData/useData';
export { useOutsideClick } from './useOutsideClick/useOutsideClick';
export { usePortal } from './usePortal/usePortal';
export { useUrlParam } from './useUrlParam/useUrlParam';
export { useUrlParamReactRouter } from './useUrlParam/useUrlParamReactRouter';
export * from './useData/useData';
export * from './useOutsideClick/useOutsideClick';
export * from './usePortal/usePortal';
export * from './useUrlParam/useUrlParam';
export * from './useUrlParam/useUrlParamReactRouter';
60 changes: 47 additions & 13 deletions lib/useData/useData.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
import { useCallback, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

type execFn = (token: AbortController) => Promise<unknown>;
export type UseDataExecuteFn = (controller: AbortController) => Promise<Response>;
export type UseDataRequestor = (fn: UseDataExecuteFn) => Promise<void>;
export type UseDataReturn<T> = [UseDataResponse<T>, UseDataRequestor];
export type UseDataStatuses = 'IDLE' | 'FETCHING' | 'RESPONDING';
export type UseDataOptions = {
debug?: boolean;
};
export type UseDataResponse<T> = {
data: T | null;
raw: Response | null;
status: UseDataStatuses;
};

const defaultResponse = { raw: null, data: null, status: 'IDLE' };

/**
* @description A custom hook to fetch data from an API.
* @param {unknown} defaultValue The default value to return if no data is fetched.
* @returns {Array} An array containing the fetched data and the request function.
* @param {boolean} options Options settings for the hook.
* @returns {T} An object or array of type T.
*/
export const useData = (defaultValue = null) => {
const [response, setResponse] = useState<unknown>(defaultValue);
const [status, setStatus] = useState('IDLE');
export function useData<T>({ debug }: UseDataOptions = { debug: false }): UseDataReturn<T> {
const [response, setResponse] = useState<UseDataResponse<T> | null>(null);
const [status, setStatus] = useState<UseDataStatuses>('IDLE');
const cancelRef = useRef<AbortController | null>(null);

const requestor = useCallback(async (fn: execFn) => {
useEffect(() => {
if (debug === true) console.log('Fetch Response: ', response);
}, [debug, response]);

useEffect(() => {
if (debug === true) console.log('Fetch Status: ', status);
}, [debug, status]);

const resp = useMemo(() => {
return {
...(response ?? defaultResponse),
status,
};
}, [response, status]);

const requestor: UseDataRequestor = useCallback(async (fn: UseDataExecuteFn) => {
async function go() {
if (cancelRef.current != null) {
cancelRef.current.abort();
cancelRef.current.abort('Canceled');
}
cancelRef.current = new AbortController();

setStatus('LOADING');
setResponse(await fn(cancelRef.current));
setStatus('FETCHING');
const res = await fn(cancelRef.current);
const data = (await res.json()) as T;
setResponse({
raw: res,
data,
status: 'RESPONDING',
});
setStatus('IDLE');
}
await go();
}, []);

return [{ ...(response ?? {}), status }, requestor];
};
return [resp, requestor] as UseDataReturn<T>;
}
9 changes: 2 additions & 7 deletions lib/useMap/useMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ type UseMapActions<K, V> = {
/** Set all key-value pairs in the map. */
setAll: (entries: MapOrEntries<K, V>) => void;
};
type UseMapReturn<K, V> = [
Omit<Map<K, V>, 'set' | 'clear' | 'delete'>,
UseMapActions<K, V>,
];
type UseMapReturn<K, V> = [Omit<Map<K, V>, 'set' | 'clear' | 'delete'>, UseMapActions<K, V>];

/**
* Custom hook that manages a key-value [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) state with setter actions.
Expand All @@ -29,9 +26,7 @@ type UseMapReturn<K, V> = [
* // Access the `map` state and use `actions` to set, remove, or reset entries.
* ```
*/
export function useMap<K, V>(
initialState: MapOrEntries<K, V> = new Map(),
): UseMapReturn<K, V> {
export function useMap<K, V>(initialState: MapOrEntries<K, V> = new Map()): UseMapReturn<K, V> {
const [map, setMap] = useState(new Map(initialState));

const actions: UseMapActions<K, V> = {
Expand Down
11 changes: 2 additions & 9 deletions lib/useOutsideClick/useOutsideClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,10 @@ import { RefObject, useEffect } from 'react';
* @param {object} doc The document object to use for event listeners.
* @returns {Array} An array containing the URL parameter value and the detected click event handler function.
*/
export const useOutsideClick = (
ref: RefObject<HTMLElement>,
fn: () => void,
doc: Document = document,
) => {
export const useOutsideClick = (ref: RefObject<HTMLElement>, fn: () => void, doc: Document = document) => {
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
ref.current != null &&
!ref.current.contains(event.target as Element)
) {
if (ref.current != null && !ref.current.contains(event.target as Element)) {
if (fn != null) fn();
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/usePortal/usePortal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ export const usePortal = () => {

console.log();

const portal = (props: PropsWithChildren) =>
createPortal(props.children, wrapper);
const portal = (props: PropsWithChildren) => createPortal(props.children, wrapper);

return portal;
};
9 changes: 2 additions & 7 deletions lib/useUrlParam/useUrlParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@ import { useState } from 'react';
* @param {string | number | null} defaultValue The default value to set if the URL parameter is not found.
* @returns {Array} An array containing the URL parameter value and a setter function.
*/
export const useUrlParam = (
param: string,
defaultValue: string | number | null = null,
) => {
export const useUrlParam = (param: string, defaultValue: string | number | null = null) => {
const { search, pathname } = window.location;
const url = new URLSearchParams(search);

const paramVal = url.get(param);
const [value, setValue] = useState<string>(
paramVal !== null ? paramVal : String(defaultValue),
);
const [value, setValue] = useState<string>(paramVal !== null ? paramVal : String(defaultValue));

function setter(val: string | ((value: string) => string)) {
if (typeof val === 'function') {
Expand Down
9 changes: 2 additions & 7 deletions lib/useUrlParam/useUrlParamReactRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ import { useHistory, useLocation } from 'react-router-dom';
* @param {string | number | null} defaultValue The default value to set if the URL parameter is not found.
* @returns {Array} An array containing the URL parameter value and a setter function.
*/
export const useUrlParamReactRouter = (
param: string,
defaultValue: string | number | null = null,
) => {
export const useUrlParamReactRouter = (param: string, defaultValue: string | number | null = null) => {
const history = useHistory();
const { search, pathname } = useLocation();
const url = new URLSearchParams(search);

const paramVal = url.get(param);
const [value, setValue] = useState<string>(
paramVal !== null ? paramVal : String(defaultValue),
);
const [value, setValue] = useState<string>(paramVal !== null ? paramVal : String(defaultValue));

function setter(val: string | ((value: string) => string)) {
if (typeof val === 'function') {
Expand Down
4 changes: 1 addition & 3 deletions setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import * as matchers from '@testing-library/jest-dom/matchers';
import { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers';
declare module 'vitest' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Assertion<T = any>
extends jest.Matchers<void, T>,
TestingLibraryMatchers<T, void> {}
interface Assertion<T = any> extends jest.Matchers<void, T>, TestingLibraryMatchers<T, void> {}
}
expect.extend(matchers);

0 comments on commit 279566b

Please sign in to comment.