React Hook: useData()

Date: 2023-06-01
export interface IUseDataState<T> {
    data: T | undefined;
    trigger: number;
    revision: number;
    loading: boolean;
    loaded: boolean;
    initialLoading: boolean;
}

export function useData<T>(fetchFn: () => Promise<T>, deps: any = []): IData<T> {

    const [state, setState] = useObjectState<IUseDataState<T>>({
        revision: 0,
        trigger: 0,
        loaded: false,
        loading: true,
        initialLoading: true,
        data: undefined
    });

    useEffectAsync(async () => {
        if (!state.initialLoading) {
            setState({ loading: true, loaded: false });
        }
        let newState: Partial<IUseDataState<T>> = {};
        try {
            const newData = await fetchFn();
            newState = { data: newData, loaded: true, revision: state.revision + 1 };
        } catch (e) {
            console.error(e);
            newState = { data: undefined, revision: state.revision + 1 };
        } finally {
            setState({ ...newState, loading: false, initialLoading: false });
        }
    }, [...deps, state.trigger]);

    return {
        data: state.data,
        revision: state.revision,
        loading: state.loading,
        loaded: state.loaded,
        initialLoading: state.initialLoading,
        setData: (data: T) => setState({ data: data, revision: state.revision + 1 }),
        updateData: () => setState({ trigger: state.trigger + 1 })
    } as IData<T>;
}

Example component:

export function UserInfo(props: { userId: string }): React.ReactElement {
    const ds = useData(() => appDomain.IUserService.loadUser(props.userId), [props.userId]);
    const user = ds.data;
    
    return <div>
        Loading: {ds.loading}
        User name: {user?.name}
        <button type="button" onClick={ds.updateData}>Refresh</button>
    </div>;
}
78270cookie-checkReact Hook: useData()