Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ import {
useRoute,
type StaticScreenProps,
} from "@react-navigation/native";
import { useAtomRefresh } from "@effect/atom-react";
import { RegistryContext, useAtomRefresh } from "@effect/atom-react";
import { pullRequestReviewKey } from "./pullRequestReviewDrafts";
import { type PullRequestDetail, type PullRequestListEntry } from "@t3tools/contracts";
import { PullRequestOverviewLoading } from "./PullRequestOverviewLoading";
import { copyPullRequestLink, sharePullRequestLink } from "./pullRequestLink";
import { pullRequestStatus } from "./pullRequestStatus";
import { useEffect, useRef, useState } from "react";
import { use, useEffect, useRef, useState } from "react";
import {
Linking,
Platform,
Expand Down Expand Up @@ -354,13 +354,21 @@ export function PullRequestRouteScreen({ route }: StaticScreenProps<PullRequestP
const stack = usePullRequestStack(params, pr);
const refreshActivity = useAtomRefresh(composerPullRequests.activity(requestTarget(params)));
const refreshGuide = useAtomRefresh(composerPullRequests.reviewGuide(requestTarget(params)));
const refresh = () => {
pullRefresh.onRefresh();
if (guidesEnabled) refreshGuide();
stack.refresh();
refreshActivity();
filesContent.refresh();
};
const registry = use(RegistryContext);
// A pull goes around the server's cache, like the web refresh button: the invalidation goes
// first so the re-reads ask the host for current checks, reviews, and state.
const refresh = () =>
pullRefresh.onRefresh(async () => {
const target = requestTarget(params);
await composerPullRequests.invalidate.run(registry, {
environmentId: target.environmentId,
input: { reference: target.input },
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if (guidesEnabled) refreshGuide();
stack.refresh();
refreshActivity();
filesContent.refresh();
});
const jumpToFiles = () => {
scrollViews.current.overview?.scrollToOffset({
offset: bodyY.current + filesY.current - insets.top,
Expand Down
50 changes: 32 additions & 18 deletions apps/mobile/src/state/use-pull-to-refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime";
import { type AsyncResult, type Atom } from "effect/reactivity";
import { use, useCallback, useEffect, useRef, useState } from "react";

/** Only a user's pull owns the refresh indicator; query invalidation stays silent. */
export function usePullToRefresh<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>> | null) {
const registry = use(RegistryContext);
const request = useRef<{ atom: NonNullable<typeof atom>; controller: AbortController } | null>(
Expand All @@ -17,24 +18,37 @@ export function usePullToRefresh<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A
[atom],
);

const onRefresh = useCallback(() => {
if (!atom || (request.current?.atom === atom && !request.current.controller.signal.aborted))
return;
request.current?.controller.abort();
const controller = new AbortController();
request.current = { atom, controller };
setRefreshingAtom(atom);
void executeAtomQuery(registry, atom, {
refresh: true,
signal: controller.signal,
reportFailure: false,
reportDefect: false,
}).finally(() => {
if (controller.signal.aborted || request.current?.controller !== controller) return;
request.current = null;
setRefreshingAtom(null);
});
}, [atom, registry]);
/**
* `beforeRefresh` runs first under the same indicator, e.g. to drop a server-side cache. If it
* fails, the read still runs and at worst answers from that cache.
*/
const onRefresh = useCallback(
(beforeRefresh?: () => Promise<unknown>) => {
if (!atom || (request.current?.atom === atom && !request.current.controller.signal.aborted))
return;
request.current?.controller.abort();
const controller = new AbortController();
request.current = { atom, controller };
setRefreshingAtom(atom);
void Promise.resolve()
.then(beforeRefresh)
.catch(() => undefined)
.then(() =>
executeAtomQuery(registry, atom, {
refresh: true,
signal: controller.signal,
reportFailure: false,
reportDefect: false,
}),
)
.finally(() => {
if (controller.signal.aborted || request.current?.controller !== controller) return;
request.current = null;
setRefreshingAtom(null);
});
},
[atom, registry],
);

return { refreshing: atom !== null && refreshingAtom === atom, onRefresh };
}
Loading