1import { RegistryContext } from "@effect/atom-react";
2import { useNavigation } from "@react-navigation/native";
3import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
4import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
5import type { Atom } from "effect/reactivity";
6
7/**
8 * Whether the Home route can be seen. Home stays mounted under a pushed
9 * Thread, and native-stack never freezes the route directly below the focused
10 * one on Fabric, so Home has to stop its own work.
11 *
12 * Driven by the native appear/disappear events rather than focus. A form sheet
13 * over Home blurs it but leaves it on screen, and the presenting screen gets no
14 * disappear event for it. Home becomes visible as soon as a back swipe or pop
15 * starts revealing it, so the list is fresh during the transition.
16 */
17export function useHomeRouteVisible(): boolean {
18 const navigation =
19 useNavigation<NativeStackNavigationProp<ReactNavigation.RootParamList, "Home">>();
20 // A cold deep link mounts Home under its Thread without ever showing it.
21 const [visible, setVisible] = useState(() => navigation.isFocused());
22 useEffect(() => {
23 const show = () => setVisible(true);
24 const removeTransitionStart = navigation.addListener("transitionStart", ({ data }) => {
25 if (!data.closing) show();
26 });
27 const removeTransitionEnd = navigation.addListener("transitionEnd", ({ data }) => {
28 if (data.closing) setVisible(false);
29 });
30 // Popping a sheet or a deep-linked route above Home has no appear event.
31 const removeFocus = navigation.addListener("focus", show);
32 return () => {
33 removeTransitionStart();
34 removeTransitionEnd();
35 removeFocus();
36 };
37 }, [navigation]);
38 return visible;
39}
40
41/**
42 * Like `useAtomValue`, but unsubscribed while `visible` is false: the last
43 * visible value is held and atom changes do not re-render. Resubscribing reads
44 * the current value in the same render that turns visibility back on.
45 */
46export function useAtomValueWhileVisible<A>(atom: Atom.Atom<A>, visible: boolean): A {
47 const registry = useContext(RegistryContext);
48 const held = useRef<{ readonly value: A } | null>(null);
49 const subscribe = useCallback(
50 (onChange: () => void) => (visible ? registry.subscribe(atom, onChange) : () => undefined),
51 [registry, atom, visible],
52 );
53 return useSyncExternalStore(subscribe, () => {
54 if (visible || held.current === null) held.current = { value: registry.get(atom) };
55 return held.current.value;
56 });
57}