I’m using PocketBase as the backend, with the frontend subscribing to it. The backend sends real-time updates to the frontend using Server-Sent Events (SSE). Everything works fine in the browser. When the app is installed as a PWA on a phone, the SSE connection is lost upon reopening the app after it has been minimized. Additionally, the page/data do not reload. The initial useEffect
block containing router.refresh()
does not trigger.
useVisibility.tsx
"use client";
import { useState, useEffect } from "react";
export function useVisibility() {
const [isVisible, setVisible] = useState(true);
useEffect(() => {
const handleVisibilityChange = () => {
setVisible(!document.hidden);
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
return isVisible;
}
AgendaLive.tsx
"use client";
import { Reservation } from "@/types";
import { useEffect } from "react";
import PocketBase from "pocketbase";
import { reservations } from "../tables";
import { useRouter } from "next/navigation";
import { useVisibility } from "@/hooks/useVisibility";
type Props = {
token: string;
date: string;
};
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
export function AgendaLive({ token, date }: Props) {
const router = useRouter();
const isVisible = useVisibility();
useEffect(() => {
if (!isVisible) return;
router.refresh();
}, [isVisible, router]);
useEffect(() => {
if (!isVisible) return;
pb.authStore.save(token);
pb.collection(reservations).subscribe<Reservation>("*", (event) => {
if (event.record.date !== date) {
return;
}
router.refresh();
});
return () => {
pb.collection(reservations).unsubscribe("*");
};
}, [token, date, router, isVisible]);
return null;
}
I’m using PocketBase as the backend, with the frontend subscribing to it. The backend sends real-time updates to the frontend using Server-Sent Events (SSE). Everything works fine in the browser. When the app is installed as a PWA on a phone, the SSE connection is lost upon reopening the app after it has been minimized. Additionally, the page/data do not reload. The initial useEffect
block containing router.refresh()
does not trigger.
useVisibility.tsx
"use client";
import { useState, useEffect } from "react";
export function useVisibility() {
const [isVisible, setVisible] = useState(true);
useEffect(() => {
const handleVisibilityChange = () => {
setVisible(!document.hidden);
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
return isVisible;
}
AgendaLive.tsx
"use client";
import { Reservation } from "@/types";
import { useEffect } from "react";
import PocketBase from "pocketbase";
import { reservations } from "../tables";
import { useRouter } from "next/navigation";
import { useVisibility } from "@/hooks/useVisibility";
type Props = {
token: string;
date: string;
};
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
export function AgendaLive({ token, date }: Props) {
const router = useRouter();
const isVisible = useVisibility();
useEffect(() => {
if (!isVisible) return;
router.refresh();
}, [isVisible, router]);
useEffect(() => {
if (!isVisible) return;
pb.authStore.save(token);
pb.collection(reservations).subscribe<Reservation>("*", (event) => {
if (event.record.date !== date) {
return;
}
router.refresh();
});
return () => {
pb.collection(reservations).unsubscribe("*");
};
}, [token, date, router, isVisible]);
return null;
}
Share
Improve this question
asked Mar 7 at 22:54
Tobias BenknerTobias Benkner
2001 gold badge2 silver badges10 bronze badges
1 Answer
Reset to default 0Sometimes, visibilitychange events are not triggered reliably. Therefore, I introduced an isConnected variable to track the connection status.
- isConnected = false when the app is left or the connection goes offline.
- isConnected = true once the connection is successfully established.
When connecting, all current data is first fully loaded. After that, the system listens for changes in real time.
Components that need real-time data can use the corresponding hook and get all the important information in one place.
usePocketbaseRealtime.tsx
import { useEffect, useState } from "react";
import PocketBase from "pocketbase";
type Props = {
collectionName: string;
filter: string;
token: string;
sort: string;
expand: string;
};
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
export function usePocketbaseRealtime<T extends { id: string }>({
collectionName,
filter,
token,
sort,
expand,
}: Props) {
const [data, setData] = useState<T[]>([]);
const [isConnected, setIsConnected] = useState(false);
const [isOnline, setIsOnline] = useState<boolean>(true);
async function setupRealtimeConnection() {
if (!isOnline) {
return;
}
pb.authStore.save(token);
try {
await pb.realtime.unsubscribe(collectionName);
const records = await pb.collection(collectionName).getFullList<T>({
filter: filter,
sort: sort,
expand: expand,
});
setData(records);
await pb.realtime.subscribe(
collectionName,
(e) => {
if (e.action === "create") {
setData((prevData) => [...prevData, e.record]);
} else if (e.action === "update") {
setData((prevData) =>
prevData.map((record) =>
record.id === e.record.id ? e.record : record
)
);
} else if (e.action === "delete") {
setData((prevData) =>
prevData.filter((record) => record.id !== e.record.id)
);
}
},
{
filter: filter,
expand: expand,
}
);
setIsConnected(true);
} catch (error) {
console.error("Error Realtime-Connection:", error);
setIsConnected(false);
}
}
async function disconnectRealtime() {
setIsConnected(false);
await pb.realtime.unsubscribe(collectionName);
}
useEffect(() => {
const handleOnline = () => {
setIsOnline(true);
setupRealtimeConnection();
};
const handleOffline = () => {
setIsOnline(false);
disconnectRealtime();
};
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
setupRealtimeConnection();
} else {
disconnectRealtime();
}
};
if (isOnline) {
setupRealtimeConnection();
}
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
document.removeEventListener("visibilitychange", handleVisibilityChange);
disconnectRealtime();
};
}, [collectionName, filter]);
return { data, isConnected, isOnline, reconnect: setupRealtimeConnection };
}