react#useTransition TypeScript Examples

The following examples show how to use react#useTransition. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useRecoilStateDeferred.ts    From nextclade with MIT License 6 votes vote down vote up
export function useRecoilStateDeferred<T>(recoilState: RecoilState<T>): [T, SetterOrUpdater<T>] {
  const [initialValue, setRecoilValue] = useRecoilState_TRANSITION_SUPPORT_UNSTABLE(recoilState)
  const [value, setValue] = useState(initialValue)
  const [, startTransition] = useTransition()
  const setValueDeferred = useCallback(
    (valOrUpdater: ValOrUpdater<T>) => {
      setValue(valOrUpdater)
      startTransition(() => {
        setRecoilValue(valOrUpdater)
      })
    },
    [setRecoilValue],
  )
  return [value, setValueDeferred]
}
Example #2
Source File: Members.tsx    From Riakuto-StartingReact-ja3.1 with Apache License 2.0 5 votes vote down vote up
Members: VFC<Props> = ({ orgCodeList, prefetch = () => undefined }) => {
  const [orgCode, setOrgCode] = useState('');
  const [input, setInput] = useState('');
  const [isPending, startTransition] = useTransition();
  const ebKey = useRef(0);

  const menuItems = orgCodeList.map((code) => ({
    key: code,
    name: capitalize(code),
    onClick: () => {
      setInput('');

      if (orgCode) {
        startTransition(() => setOrgCode(code));
      } else {
        setOrgCode(code);
      }
    },
    onMouseOver: () => prefetch(code),
    active: code === orgCode,
  }));

  const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
    event.preventDefault();
    setOrgCode(input.toLowerCase().trim());
  };

  return (
    <>
      <header className="app-header">
        <h1>組織メンバーリスト</h1>
      </header>
      <form className="search-form" onSubmit={handleSubmit}>
        <Input
          placeholder="組織コードを入力..."
          type="text"
          value={input}
          onChange={(_, data) => setInput(data.value)}
        />
        <Button type="submit" disabled={isPending} primary>
          検索
        </Button>
      </form>
      <Menu items={menuItems} text />
      <Divider />
      <div className={isPending ? 'loading' : ''}>
        <ErrorBoundary
          statusMessages={{
            404: `‘${orgCode}’ というコードの組織は見つかりません`,
          }}
          onError={() => {
            ebKey.current += 1;
          }}
          key={ebKey.current}
        >
          <SuspenseList revealOrder="forwards">
            <Suspense fallback={<Spinner size="small" />}>
              <OrgInfo orgCode={orgCode} />
            </Suspense>
            <Suspense fallback={<Spinner size="large" />}>
              <MemberList orgCode={orgCode} />
            </Suspense>
          </SuspenseList>
        </ErrorBoundary>
      </div>
    </>
  );
}
Example #3
Source File: Members2.tsx    From Riakuto-StartingReact-ja3.1 with Apache License 2.0 5 votes vote down vote up
Members: VFC<Props> = ({ orgCodeList, prefetch = () => undefined }) => {
  const [orgCode, setOrgCode] = useState('');
  const [input, setInput] = useState('');
  const [isPending, startTransition] = useTransition();
  const { reset } = useQueryErrorResetBoundary();

  const menuItems = orgCodeList.map((code) => ({
    key: code,
    name: capitalize(code),
    onClick: () => {
      setInput('');

      if (orgCode) {
        startTransition(() => setOrgCode(code));
      } else {
        setOrgCode(code);
      }
    },
    onMouseOver: () => prefetch(code),
    active: code === orgCode,
  }));

  const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
    event.preventDefault();
    setOrgCode(input.toLowerCase().trim());
  };

  return (
    <>
      <header className="app-header">
        <h1>組織メンバーリスト</h1>
      </header>
      <form className="search-form" onSubmit={handleSubmit}>
        <Input
          placeholder="組織コードを入力..."
          type="text"
          value={input}
          onChange={(_, data) => setInput(data.value)}
        />
        <Button type="submit" disabled={isPending} primary>
          検索
        </Button>
      </form>
      <Menu items={menuItems} text />
      <Divider />
      <div className={isPending ? 'loading' : ''}>
        <ErrorBoundary
          fallbackRender={({ resetErrorBoundary }) => (
            <>
              <Message warning>
                {orgCode} というコードの組織は見つかりません
              </Message>
              <Button color="olive" onClick={() => resetErrorBoundary()}>
                エラーをリセット
              </Button>
            </>
          )}
          onReset={() => reset()}
        >
          <SuspenseList revealOrder="forwards">
            <Suspense fallback={<Spinner size="small" />}>
              <OrgInfo orgCode={orgCode} />
            </Suspense>
            <Suspense fallback={<Spinner size="large" />}>
              <MemberList orgCode={orgCode} />
            </Suspense>
          </SuspenseList>
        </ErrorBoundary>
      </div>
    </>
  );
}
Example #4
Source File: rauta.tsx    From tobira with Apache License 2.0 4 votes vote down vote up
makeRouter = <C extends Config, >(config: C): RouterLib => {
    // Helper to log debug messages if `config.debug` is true.
    const debugLog = (...args: any[]) => {
        if (config.debug) {
            // eslint-disable-next-line no-console
            console.debug("[rauta] ", ...args);
        }
    };

    /** What rauta stores in the history state */
    type _State = {
        /** The last scroll position of the route */
        scrollY: number;
        /** An increasing number. Is used to undo popstate events. */
        index: number;
    };

    // We maintain an index in a variable here to know the "previous index" when
    // we are reacting to "onPopState" events.
    let currentIndex = 0;

    // If the page is first loaded, we want to correctly set the state.
    if (window.history.state?.index == null) {
        window.history.replaceState({ index: 0, ...window.history.state }, "");
    }
    if (window.history.state?.scrollY == null) {
        window.history.replaceState({ scrollY: window.scrollY, ...window.history.state }, "");
    }

    /** Wrapper for `history.pushState` */
    const push = (url: string) => {
        const index = currentIndex + 1;
        window.history.pushState({ scrollY: 0, index }, "", url);
        currentIndex = index;
    };

    /** Wrapper for `history.replaceState`. If `url` is `null`, it is not changed. */
    const replace = (url: string | null, scrollY: number) => {
        const state = {
            scrollY,
            index: currentIndex,
        };
        window.history.replaceState(state, "", url ?? undefined);
    };

    const updateScrollPos = () => replace(null, window.scrollY);


    const shouldPreventNav = (listeners: Listeners<BeforeNavListener>): boolean => {
        for (const { listener } of listeners) {
            if (listener() === "prevent-nav") {
                return true;
            }
        }

        return false;
    };

    const useRouterImpl = (caller: string): RouterControl => {
        const context = React.useContext(Context);
        if (context === null) {
            return bug(`${caller} used without a parent <Router>! That's not allowed.`);
        }

        return {
            isTransitioning: context.isTransitioning,
            push,
            replace: (url: string) => replace(url, window.scrollY),
            listenAtNav: (listener: AtNavListener) =>
                context.listeners.atNav.add(listener),
            listenBeforeNav: (listener: BeforeNavListener) =>
                context.listeners.beforeNav.add(listener),
            goto: (uri: string): void => {
                updateScrollPos();

                if (shouldPreventNav(context.listeners.beforeNav)) {
                    return;
                }

                const href = new URL(uri, document.baseURI).href;
                const newRoute = matchRoute(href);

                // When navigating to new routes, the scroll position always
                // starts as 0 (i.e. the very top).
                context.setActiveRoute({ route: newRoute, initialScroll: 0 });
                push(href);

                debugLog(`Setting active route for '${href}' (index ${currentIndex}) `
                    + "to: ", newRoute);
            },
        };
    };

    const Link = ({ to, children, onClick, ...props }: LinkProps) => {
        const router = useRouterImpl("<Link>");

        const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
            // If the caller specified a handler, we will call it first.
            onClick?.(e);

            // We only want to react to simple mouse clicks.
            if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button !== 0) {
                return;
            }

            e.preventDefault();
            router.goto(to);
        };

        return <a href={to} onClick={handleClick} {...props}>{children}</a>;
    };

    const matchRoute = (href: string): MatchedRoute => {
        const url = new URL(href);
        for (const route of config.routes) {
            const matched: MatchedRoute | null = route.match(url);

            if (matched !== null) {
                return matched;
            }
        }

        return config.fallback.prepare(url);
    };

    const matchInitialRoute = (): MatchedRoute => matchRoute(window.location.href);

    type ActiveRoute = {
        route: MatchedRoute;

        /** A scroll position that should be restored when the route is first rendered */
        initialScroll: number | null;
    };

    type ContextData = {
        activeRoute: ActiveRoute;
        setActiveRoute: (newRoute: ActiveRoute) => void;
        listeners: {
            atNav: Listeners<AtNavListener>;
            beforeNav: Listeners<BeforeNavListener>;
        };
        isTransitioning: boolean;
    };

    const Context = React.createContext<ContextData | null>(null);

    const useRouter = (): RouterControl => useRouterImpl("`useRouter`");

    /** Provides the required context for `<Link>` and `<ActiveRoute>` components. */
    const Router = ({ initialRoute, children }: RouterProps) => {
        const listeners = useRef<ContextData["listeners"]>({
            atNav: new Listeners(),
            beforeNav: new Listeners(),
        });
        const [activeRoute, setActiveRouteRaw] = useState<ActiveRoute>({
            route: initialRoute,
            initialScroll: window.history.state?.scrollY ?? null,
        });
        const [isPending, startTransition] = useTransition();

        const setActiveRoute = (newRoute: ActiveRoute) => {
            startTransition(() => {
                setActiveRouteRaw(() => newRoute);
                listeners.current.atNav.callAll([]);
            });
        };

        // Register some event listeners and set global values.
        useEffect(() => {
            // Whenever the user navigates forwards or backwards in the browser,
            // we have to render the corresponding route. We also restore the
            // scroll position which we store within the history state.
            // Finally, this is used to prevent navigating away from a route if
            // this is blocked.
            let ignoreNextPop = false;
            const onPopState = (e: PopStateEvent) => {
                // This is unfortunately necessary since we need to use `go()`
                // below to undo some navigation. So we trigger this event ourselves.
                // But that we want to ignore. This boolean variable is not 100%
                // guaranteed to work as intended. There could be `popstate` events
                // already queued or happening during this handler function. So we
                // might ignore the wrong event. But we have not found any way to
                // improve this situation, unfortunately. AND: we weren't able to
                // trigger any weird behavior in practice, so that's good.
                if (ignoreNextPop) {
                    ignoreNextPop = false;
                    return;
                }

                const newIndexRaw = e.state?.index;
                const newIndex = typeof newIndexRaw === "number" ? newIndexRaw : null;
                debugLog(`Handling popstate event to '${window.location.href}' `
                    + `(indices ${currentIndex} -> ${newIndex})`);
                if (shouldPreventNav(listeners.current.beforeNav)) {
                    // We want to prevent the browser from going backwards or forwards.
                    // Unfortunately, `e.preventDefault()` does nothing as the event is not
                    // cancelable. So we can only undo the change, at least most of the time.
                    if (newIndex != null) {
                        // The state that was transitioned to was controlled by us (this will
                        // be the case most of the time). We ignore a delta of 0
                        const delta = currentIndex - newIndex;
                        debugLog(`Undoing popstate event via go(${delta})`);
                        ignoreNextPop = true;
                        window.history.go(delta);
                        return;
                    } else {
                        // There was no index stored in the state. This should almost never happen,
                        // except if other JS code here uses the `history` API directly. If the
                        // forward/backward buttons direct to a non-Tobira site, the "popstate"
                        // event is not fired, but the onbeforeunload is triggered.
                        //
                        // If this happens, we do not `return` and actually render the correct
                        // route. Otherwise we have a strange inconsistent app state.
                        debugLog("Can't undo popstate event :-(");
                    }
                }

                currentIndex = newIndex ?? 0;

                const newRoute = matchRoute(window.location.href);
                setActiveRoute({ route: newRoute, initialScroll: e.state?.scrollY });
                debugLog(
                    "Reacting to 'popstate' event: setting active route for"
                        + `'${window.location.href}' to: `,
                    newRoute,
                );
            };

            // To prevent the browser restoring any scroll position.
            history.scrollRestoration = "manual";

            // We want an up2date scroll value in our history state. Always
            // updating it in onScroll unfortunately lead to laggy behavior on
            // some machines. So we are debouncing here.
            let timer: ReturnType<typeof setTimeout>;
            const onScroll = () => {
                clearTimeout(timer);
                timer = setTimeout(() => updateScrollPos(), 200);
            };

            window.addEventListener("popstate", onPopState);
            window.addEventListener("scroll", onScroll);
            window.addEventListener("beforeunload", updateScrollPos);
            return () => {
                window.removeEventListener("popstate", onPopState);
                window.removeEventListener("scroll", onScroll);
                window.removeEventListener("beforeunload", updateScrollPos);
            };
        }, []);

        // Dispose of routes when they are no longer needed.
        useEffect(() => () => {
            if (activeRoute.route.dispose) {
                debugLog("Disposing of route: ", activeRoute);
                activeRoute.route.dispose();
            }
        }, [activeRoute]);

        const contextData = {
            setActiveRoute,
            activeRoute,
            listeners: listeners.current,
            isTransitioning: isPending,
        };

        return <Context.Provider value={contextData}>{children}</Context.Provider>;
    };

    const ActiveRoute = () => {
        const context = React.useContext(Context);
        if (context === null) {
            throw new Error("<ActiveRoute> used without a parent <Router>! That's not allowed.");
        }

        useEffect(() => {
            const scroll = context.activeRoute.initialScroll;
            if (scroll != null) {
                debugLog("Restoring scroll position to: ", scroll);
                window.scrollTo(0, scroll);
            }
        }, [context.activeRoute]);

        return context.activeRoute.route.render();
    };

    return {
        Link,
        matchRoute,
        matchInitialRoute,
        useRouter,
        ActiveRoute,
        Router,
    };
}