Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useAppIsStale #300

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,58 @@ export async function loader({ request }: LoaderFunctionArgs) {
}
```

### Detect if Application is Stale

> **Note**
> This depends on `react` and `@remix-run/react`.

The `useAppIsStale` hook lets you detect if the application client is stale compared to the server version, this means the user is seeing an old version of the application and should reload the page.

```tsx
import { useAppIsStale } from "remix-utils/use-app-is-stale";
sergiodxa marked this conversation as resolved.
Show resolved Hide resolved

export default function Component() {
let isStale = useAppIsStale();

// Run an effect to ask the user to reload
useEffect(() => {
if (!isStale) return;
if (window.confirm("The app is stale. Reload?")) window.location.reload();
}, [isStale]);

// Or render a UI to tell the user to reload.
if (isStale) {
return (
<div>
<p>There's a new version of the app, please reload the page</p>
<button onClick={() => window.location.reload()}>Reload</button>
</div>
);
}

return <div>Normal content</div>;
}
```

For this hook to work is required an API endpoint to compare the client vs the server version.

The default endpoint is expected to work under `/api/version`, and the example content is:

```ts
// app/routes/api.version.ts
import { json } from "@remix-run/node"; // or /cloudflare or /deno
import * as build from "@remix-run/dev/server-build";

export async function loader() {
return json({ version: build.assets.version });
}
```

The shape must always be `{ version: string }`.\

> ![TIP]
> Use `useAppIsStale({ path: "/api/v1/version" })` to customize the path of the API endpoint.

## Author

- [Sergio Xalambrí](https://sergiodxa.com)
Expand Down
52 changes: 52 additions & 0 deletions src/react/use-app-is-stale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useFetcher } from "@remix-run/react";
import { useEffect } from "react";
import { useHydrated } from "./use-hydrated.js";

interface AppIsStaleOptions {
/**
* The path of the endpoint used to check the server version.
* @default "/api/version"
*/
path?: string;
/**
* The interval in milliseconds to check the server version.
* @default 5000
*/
intervalMs?: number;
}

/**
* Checks if the app is stale by comparing the server version with the client
* version.
* @param options The options for the hook.
* @returns `true` if the app is stale, `false` otherwise.
* @example
* let isStale = useAppIsStale();
* useEffect(() => {
* if (!isStale) return;
* if (window.confirm("The app is stale. Reload?")) {
* window.location.reload();
* }
* }, [isStale]);
*/
export function useAppIsStale({
path = "/api/version",
intervalMs = 5000,
}: AppIsStaleOptions = {}) {
let isHydrated = useHydrated();
let { load, data } = useFetcher<{ version: string }>();

let isStale = false;

if (isHydrated && data) {
isStale = data.version !== window.__remixManifest?.version;
}

useEffect(() => {
if (isStale) return;
let id = setInterval(() => load(path), intervalMs);
return () => clearInterval(id);
}, [load, path, intervalMs, isStale]);

return isStale;
}
Loading