React Hooks: Helpers

Date: 2022-10-24

useDataSource()

import { Dispatch, SetStateAction, useEffect, useState } from "react";

export interface IData<T> {
    data: T;
    loading: boolean;
    loaded: boolean;
    setData: Dispatch<SetStateAction<T>>;
    updateData: () => any;
}

export function useData<T>(fetchFn: () => Promise<T>, deps: any = []): IData<T> {
    const [trigger, setTrigger] = useState<boolean>(false);
    const [data, setData] = useState<T>();
    const [loading, setLoading] = useState(true);
    const [loaded, setLoaded] = useState(false);
    useEffect(() => {
        async function fetch() {
            setLoading(true);
            setLoaded(false);
            try {
                const newData = await fetchFn();
                setData(newData);
                setLoaded(true);
            } catch (e) {
                console.error(e);
                setData(undefined);
            } finally {
                setLoading(false);
            }
        }
        fetch();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...deps, trigger]);
    return {
        data,
        loading,
        loaded,
        setData,
        updateData: () => setTrigger(!trigger)
    } as IData<T>;
};

useInterval()

/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";

function useInterval(delay: number) {
    const [data, setData] = useState<number>(0);
    let index = 0;
    useEffect(() => {
        if (!delay && delay !== 0) { return; }
        const id = setInterval(() => setData(index++), delay);
        return () => clearInterval(id);
    }, [delay]);
    return [data, setData];
}
export default useInterval;

useObservable() + useFetch() example

/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import { Observable } from "rxjs";

import { Select } from "antd";
import { IUser } from "../../domain/user/IUser";
import { appDomain, translate } from "../../domain/wiring/AppDomain";


export function useFetch<T>(fetchFn: () => Promise<T[]>, deps: any[] = []): [T[], Dispatch<SetStateAction<T[]>>] {
    const [data, setData] = useState<T[]>([]);
    useEffect(() => {
        async function fetch() {
            const newData = await fetchFn();
            setData(newData);
        }
        fetch();
    }, deps);
    return [data, setData];
};


interface IProps {
    value?: string;
    onChange: (value: string) => void;
}
export function UserSelector(props: IProps) {
    const company = useObservable(appDomain.ICompanyService.companyObservable());
    const [users] = useFetch<IUser>(() => appDomain.IRequestUser.getAllUsersForCompany(company?.id), [company?.id]);
    return <Select
        showSearch
        style={{ minWidth: 200 }}
        placeholder={translate("general.select.user")}
        optionFilterProp="children"
        value={props.value}
        onChange={props.onChange}>
        {users.map((x: IUser, i: number) => (<Select.Option key={x.id} value={x.id!}>{x.fullName}</Select.Option>))}
    </Select>;
}

export function useObservable<T>(subject: Observable<T | undefined>, initialValue?: T): T | undefined {
    const [data, setData] = useState<T | undefined>(initialValue);
    useEffect(() => {
        if (!subject) { return; }
        const subscription = subject.subscribe((x: any) => {
            setData(x);
        });
        return () => subscription.unsubscribe();
    }, [subject]);
    return data;
}

export function useObservableReducer<T>(subject?: Observable<T | undefined>): T | undefined {
    const [data, forceUpdate] = useReducer(x => x + 1, 0);
    useEffect(() => {
        if (!subject) { return; }
        const subscription = subject.subscribe(() => {
            forceUpdate();
        });
        return () => subscription.unsubscribe();
    }, [subject]);
    return data;
}

Old code (with useSubscription)

/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState, Dispatch, SetStateAction } from "react";
import { Subscription } from "rxjs";

import { Select } from "antd";
import { IUser } from "../../domain/user/IUser";
import { appDomain, translate } from "../../domain/wiring/AppDomain";

type ChangeFn<T> = (next: T | undefined) => any;
type SubscribeFn<T> = (nextFn: ChangeFn<T>) => Subscription;

export function useSubscription<T>(subscribeFn: SubscribeFn<T>, deps?: any[]): T | undefined {
    const [data, setData] = useState<T | undefined>(undefined);
    useEffect(() => {
        function onChange(next: T | undefined) {
            setData(next);
        }
        const subscription = subscribeFn(onChange);
        return () => subscription.unsubscribe();
    }, deps);
    return data;
};

export function useDatasource<T>(fetchFn: () => Promise<T[]>, deps: any[]): [T[], Dispatch<SetStateAction<T[]>>] {
    const [data, setData] = useState<T[]>([]);
    useEffect(() => {
        async function fetch() {
            const newData = await fetchFn();
            setData(newData);
        }
        fetch();
    }, deps);
    return [data, setData];
};

/* Example usage */

interface IProps {
    value?: string;
    onChange: (value: string) => void;
}
export function UserSelector(props: IProps) {
    const company = useSubscription(appDomain.ICompanyService.subscribeToCompany.bind(appDomain.ICompanyService));
    const [users] = useDatasource<IUser>(() => appDomain.IRequestUser.getAllUsersForCompany(company?.id), [company?.id]);
    return <Select
        showSearch
        style={{ minWidth: 200 }}
        placeholder={translate("general.select.user")}
        optionFilterProp="children"
        value={props.value}
        onChange={props.onChange}>
        {users.map((x: IUser, i: number) => (<Select.Option key={x.id} value={x.id!}>{x.fullName}</Select.Option>))}
    </Select>;
}


68760cookie-checkReact Hooks: Helpers