Skip to content

Instantly share code, notes, and snippets.

@wojtekmaj
Last active November 25, 2024 04:51
Show Gist options
  • Select an option

  • Save wojtekmaj/da7fb5908cf30a9a91a1243c663dea91 to your computer and use it in GitHub Desktop.

Select an option

Save wojtekmaj/da7fb5908cf30a9a91a1243c663dea91 to your computer and use it in GitHub Desktop.

Revisions

  1. wojtekmaj revised this gist Feb 26, 2024. No changes.
  2. wojtekmaj revised this gist Feb 26, 2024. No changes.
  3. wojtekmaj created this gist Feb 26, 2024.
    72 changes: 72 additions & 0 deletions use.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    /// <reference types="react/next" />
    import { use as originalUse, useContext } from 'react';

    const STATUS = {
    PENDING: 'pending',
    REJECTED: 'rejected',
    FULFILLED: 'fulfilled',
    } as const;

    type TState<T> =
    | { status: typeof STATUS.PENDING; promise: Promise<void> }
    | { status: typeof STATUS.REJECTED; error: Error }
    | { status: typeof STATUS.FULFILLED; result: T };

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const states = new Map<Promise<unknown>, TState<any>>();

    function usePromiseFallback<T>(usable: Promise<T>): T {
    const existingState = states.get(usable);

    const state: TState<T> =
    existingState ||
    (() => {
    const promise = usable
    .then((data) => {
    states.set(usable, {
    status: STATUS.FULFILLED,
    result: data,
    });
    })
    .catch((error) => {
    states.set(usable, {
    status: STATUS.REJECTED,
    error,
    });
    });

    const newState: TState<T> = { status: 'pending', promise: promise };
    states.set(usable, newState);
    return newState;
    })();

    switch (state.status) {
    case STATUS.PENDING:
    // Suspend the component while fetching
    throw state.promise;
    case STATUS.REJECTED:
    // Result is an error
    throw state.error;
    case STATUS.FULFILLED:
    // Result is a fulfilled promise
    return state.result;
    }
    }

    function useContextFallback<T>(context: React.Context<T>) {
    return useContext(context);
    }

    function useFallback<T>(usable: Promise<T> | React.Context<T>): T {
    if (usable instanceof Promise) {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    return usePromiseFallback(usable);
    }

    // eslint-disable-next-line react-hooks/rules-of-hooks
    return useContextFallback(usable);
    }

    const use = originalUse || useFallback;

    export default use;