File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next";
1515import { SafeAreaView } from "react-native-safe-area-context" ;
1616import { listItems , archiveItem } from "../../src/api/items" ;
1717import { reportClientError } from "../../src/api/report" ;
18+ import { useRefreshOnFocus } from "../../src/lib/useRefreshOnFocus" ;
1819import { ItemCard } from "../../src/components/ItemCard" ;
1920import { useAuth } from "../../src/store/auth" ;
2021import { colors , spacing , radius , font } from "../../src/theme" ;
@@ -79,6 +80,14 @@ export default function ItemsScreen() {
7980 load ( 1 , search ) ;
8081 } , [ search , filter ] ) ;
8182
83+ // Silently refresh page 1 when the tab is re-focused or the app returns to the
84+ // foreground, so items added on another device show up. Initial mount load is
85+ // handled by the effect above, so the first focus is skipped.
86+ useRefreshOnFocus ( ( ) => {
87+ setPage ( 1 ) ;
88+ load ( 1 , search ) ;
89+ } ) ;
90+
8291 function onRefresh ( ) {
8392 setRefreshing ( true ) ;
8493 setPage ( 1 ) ;
Original file line number Diff line number Diff line change 1- import React , { useEffect , useState } from "react" ;
1+ import React , { useState } from "react" ;
22import {
33 View ,
44 Text ,
@@ -23,6 +23,7 @@ import {
2323 setTagColor ,
2424} from "../../src/api/tags" ;
2525import { reportClientError } from "../../src/api/report" ;
26+ import { useRefreshOnFocus } from "../../src/lib/useRefreshOnFocus" ;
2627import { colors , spacing , radius , font } from "../../src/theme" ;
2728import type { TagWithCount } from "@notes-world/shared" ;
2829
@@ -67,9 +68,9 @@ export default function TagsScreen() {
6768 }
6869 }
6970
70- useEffect ( ( ) => {
71- load ( ) ;
72- } , [ ] ) ;
71+ // Load on first focus and refresh whenever the tab is re-focused or the app
72+ // returns to the foreground, so tags created on another device appear.
73+ useRefreshOnFocus ( load , { immediate : true } ) ;
7374
7475 function onRefresh ( ) {
7576 setRefreshing ( true ) ;
Original file line number Diff line number Diff line change 1+ import { useCallback , useEffect , useRef } from "react" ;
2+ import { AppState } from "react-native" ;
3+ import { useFocusEffect } from "expo-router" ;
4+
5+ /**
6+ * Refetch when the screen regains focus (tab switch / returning from a detail
7+ * screen) and when the app returns to the foreground, so data created on another
8+ * device (e.g. the web app) shows up without a manual pull-to-refresh.
9+ *
10+ * The latest callback is held in a ref so callers can pass an inline function
11+ * without re-subscribing every render. The first focus (initial mount) is
12+ * skipped by default so screens that already load on mount don't double-fetch;
13+ * pass { immediate: true } to also run on that first focus.
14+ */
15+ export function useRefreshOnFocus (
16+ refetch : ( ) => void ,
17+ opts : { immediate ?: boolean } = { } ,
18+ ) : void {
19+ const cb = useRef ( refetch ) ;
20+ cb . current = refetch ;
21+ const firstFocus = useRef ( true ) ;
22+ const immediate = opts . immediate ?? false ;
23+
24+ useFocusEffect (
25+ useCallback ( ( ) => {
26+ if ( firstFocus . current ) {
27+ firstFocus . current = false ;
28+ if ( ! immediate ) return ;
29+ }
30+ cb . current ( ) ;
31+ } , [ immediate ] ) ,
32+ ) ;
33+
34+ useEffect ( ( ) => {
35+ const sub = AppState . addEventListener ( "change" , ( state ) => {
36+ if ( state === "active" ) cb . current ( ) ;
37+ } ) ;
38+ return ( ) => sub . remove ( ) ;
39+ } , [ ] ) ;
40+ }
You can’t perform that action at this time.
0 commit comments