Skip to content

Instantly share code, notes, and snippets.

@wesleycole
Created September 24, 2021 01:55
Show Gist options
  • Select an option

  • Save wesleycole/04dac6eaf352c0c58b25a2cb096486ae to your computer and use it in GitHub Desktop.

Select an option

Save wesleycole/04dac6eaf352c0c58b25a2cb096486ae to your computer and use it in GitHub Desktop.

Revisions

  1. wesleycole created this gist Sep 24, 2021.
    17 changes: 17 additions & 0 deletions usage.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    function App() {
    const { data, isLoading, error, refetch } = useQuery(
    async () => await DataStore.query(Model),
    );

    const { handler, isLoading, isError, isSuccess } = useMutation<Model>(async (variables) => {
    await DataStore.save(new Sermon(template));
    });

    useSubscription(() => DataStore.observe(Model), {
    onUpdate: refetch,
    })

    return (
    <button onClick={() => handler({})}>Perform the mutation</button>
    )
    }
    53 changes: 53 additions & 0 deletions useMutation.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    import { DependencyList, useCallback, useState } from 'react';

    interface UseMutationOptions {
    deps?: DependencyList;
    }

    type State = 'idle' | 'loading' | 'error' | 'success';

    export function useMutation<T>(
    fn: any,
    {
    deps = [],
    onSuccess,
    onError,
    }: UseMutationOptions & {
    onSuccess?: () => void;
    onError?: (error: any) => void;
    } = {},
    ): {
    isLoading: boolean;
    isError: boolean;
    isSuccess: boolean;
    state: State;
    error: any;
    handler: (args: any) => Promise<T>;
    } {
    const [state, setState] = useState<State>('idle');
    const [error, setError] = useState();

    const isLoading = state === 'loading';
    const isError = state === 'error';
    const isSuccess = state === 'success';

    const callback = useCallback((...args: any): any => fn(...args), deps);

    async function handler(args: any) {
    setState('loading');

    try {
    const result = await callback(args);
    setState('success');
    onSuccess?.();

    return result;
    } catch (err) {
    setState('error');
    setError(err);
    onError?.(error);
    }
    }

    return { isLoading, isError, isSuccess, state, error, handler };
    }
    83 changes: 83 additions & 0 deletions useQuery.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    import {
    DependencyList,
    useCallback,
    useEffect,
    useRef,
    useState,
    } from 'react';
    import { useIsMounted } from './utils/useIsMounted';

    type FunctionReturningPromise = (...args: any[]) => Promise<any>;
    type PromiseType<P extends Promise<any>> = P extends Promise<infer T>
    ? T
    : never;

    export type AsyncState<T> =
    | {
    isLoading: boolean;
    error?: undefined;
    data?: undefined;
    }
    | {
    isLoading: true;
    error?: Error | undefined;
    data?: T;
    }
    | {
    isLoading: false;
    error: Error;
    data?: undefined;
    }
    | {
    isLoading: false;
    error?: undefined;
    data: T;
    };

    type State<T extends FunctionReturningPromise> = AsyncState<
    PromiseType<ReturnType<T>>
    >;

    export function useQuery<T extends FunctionReturningPromise>(
    fn: T,
    options: {
    deps?: DependencyList;
    initialState?: State<T>;
    onFetch?: (args: any) => void;
    } = {},
    ) {
    const initialState = options?.initialState || { isLoading: false };
    const lastCallId = useRef(0);
    const isMounted = useIsMounted();
    const [state, setState] = useState<State<T>>(initialState);

    const callback = useCallback((...args: []): ReturnType<T> => {
    const callId = ++lastCallId.current;
    setState((prevState) => ({ ...prevState, loading: true }));

    return fn(...args).then(
    (data) => {
    isMounted() &&
    callId === lastCallId.current &&
    setState({ data, isLoading: false });

    options?.onFetch?.(data);

    return data;
    },
    (error) => {
    isMounted() &&
    callId === lastCallId.current &&
    setState({ error, isLoading: false });

    return error;
    },
    ) as ReturnType<T>;
    }, options?.deps || []);

    useEffect(() => {
    callback();
    }, [callback]);

    return { ...state, refetch: callback };
    }
    47 changes: 47 additions & 0 deletions useSubscription.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    import { DependencyList, useCallback, useEffect } from 'react';

    enum OpType {
    INSERT = 'INSERT',
    UPDATE = 'UPDATE',
    DELETE = 'DELETE',
    }

    type SubscriptionMessage = {
    opType: OpType;
    element: any;
    model: any;
    condition: any;
    };

    export interface UseSubscriptionOptions {
    deps?: DependencyList;
    refetch?: () => void;
    onInsert?: () => void;
    onUpdate?: () => void;
    onDelete?: () => void;
    }

    export function useSubscription<T>(
    fn: any,
    {
    deps = [],
    refetch,
    onInsert,
    onUpdate,
    onDelete,
    }: UseSubscriptionOptions = {},
    ) {
    const callback = useCallback((...args: []): any => fn(...args), deps);

    useEffect(() => {
    const subscription = callback().subscribe((msg: SubscriptionMessage) => {
    refetch?.();

    if (msg?.opType === 'INSERT') onInsert?.();
    if (msg?.opType === 'UPDATE') onUpdate?.();
    if (msg?.opType === 'DELETE') onDelete?.();
    });

    return () => subscription.unsubscribe();
    }, [callback]);
    }