Kärnfunktion: dataflöde med offline-stöd
Bygg det huvudsakliga dataflödet med React Query och beständig offline-lagring, implementera dra för att uppdatera och paginering, lägg till mutationer för att skapa och ta bort data och animera listobjekt när de visas.
Kärnfunktion: dataflöde med offline-stöd är en gratis lektion i React Native Academy på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för React Native Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i React Native Academy innehåller totalt 4 lektioner.
Vad är offline-first-design
Offline-first-design innebär att appen fungerar korrekt utan internetanslutning – användare kan visa cachade data, utföra ändringar som köas för synkronisering och slipper felmeddelanden bara för att nätverket inte är tillgängligt. Mobilanvändare rör sig ofta in och ut ur områden med uppkoppling. En React Native-app som slutar fungera utan nätverk känns bräcklig, medan en app som fortsätter fungera känns genomarbetad och pålitlig.
Konfigurera QueryClient
React Querys QueryClient utgör grunden för datalagret. Konfigurera den med rimliga standardvärden: en staleTime som undviker onödiga omladdningar (till exempel 5 minuter för data som ändras sällan) och en gcTime (tid för skräpsamling) som behåller oanvända data i cachen tillräckligt länge för att kunna leverera dem från cachen när anslutningen återupprättas. Skicka den här klienten till QueryClientProvider i appens rot.
// src/services/queryClient.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes — don't refetch too often
gcTime: 1000 * 60 * 60, // Keep in cache 1 hour after unmount
retry: 2,
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
refetchOnWindowFocus: false, // Not needed on mobile
refetchOnReconnect: true, // Sync when network returns
},
},
});Spara Query-cachen med AsyncStorage
För att göra data tillgängliga offline sparar du React Query-cachen i AsyncStorage. Använd @tanstack/react-query-persist-client tillsammans med en AsyncStorage-persister. När appen startar återställs den sparade cachen innan den första nätverksbegäran skickas — så användarna ser sin lista över vanor direkt, även innan hämtningen är klar. Cachen hydreras asynkront, så visa cachad data optimistiskt och uppdatera när den aktuella datan anländer.
// App.tsx
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { queryClient } from './src/services/queryClient';
const asyncStoragePersister = createAsyncStoragePersister({
storage: AsyncStorage,
key: 'REACT_QUERY_CACHE',
});
export default function App() {
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister: asyncStoragePersister }}
>
<AuthProvider>
<RootNavigator />
</AuthProvider>
</PersistQueryClientProvider>
);
}Hämta vanelistan med useQuery
Bygg en anpassad hook, useHabits, som kapslar in Supabase-frågan i useQuery. Frågenyckeln ['habits', userId] begränsar cachen till den aktuella användaren och förhindrar att data läcker mellan konton. React Query returnerar data, isLoading och error — skicka alla tre till skärmen så att rätt UI-tillstånd kan visas vid varje tidpunkt.
// src/hooks/useHabits.ts
import { useQuery } from '@tanstack/react-query';
import { supabase } from '../services/supabase';
import { useAuth } from '../context/AuthContext';
import { Habit } from '../types';
async function fetchHabits(userId: string): Promise<Habit[]> {
const { data, error } = await supabase
.from('habits')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: true });
if (error) throw error;
return data;
}
export function useHabits() {
const { session } = useAuth();
const userId = session!.user.id;
return useQuery({
queryKey: ['habits', userId],
queryFn: () => fetchHabits(userId),
enabled: !!userId,
});
}Rendera FlatList med cachad data
Använd hooken useHabits på skärmen HabitList. Visa en skeleton-laddare eller en spinner vid den första laddningen, använd arrayen data direkt som källa för FlatList och hantera feltillståndet med en knapp för att försöka igen. Eftersom cachen sparas är data omedelbart tillgänglig när appen startas igen (ingen synlig laddningsblinkning), medan React Query hämtar en aktuell version i bakgrunden.
// src/screens/habits/HabitListScreen.tsx
import { FlatList, ActivityIndicator, View, Text } from 'react-native';
import { useHabits } from '../../hooks/useHabits';
import { HabitCard } from '../../components/HabitCard';
export function HabitListScreen() {
const { data: habits, isLoading, error, refetch } = useHabits();
if (isLoading && !habits) {
return <ActivityIndicator style={{ flex: 1 }} />;
}
if (error) {
return (
<View>
<Text>Failed to load habits.</Text>
<Button title='Retry' onPress={() => refetch()} />
</View>
);
}
return (
<FlatList
data={habits}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <HabitCard habit={item} />}
onRefresh={refetch}
refreshing={isLoading}
/>
);
}Skapa vanor med useMutation
useMutation hanterar infogningsåtgärden. Anropa queryClient.invalidateQueries(['habits', userId]) i onSuccess för att utlösa en ny hämtning av vanelistan. Då visas den nyskapade vanan i listan direkt efter att den har skapats, utan att du behöver uppdatera cachen manuellt. Ange onError så att användaren får ett felmeddelande om infogningen misslyckas.
// src/hooks/useCreateHabit.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '../services/supabase';
import { useAuth } from '../context/AuthContext';
export function useCreateHabit() {
const queryClient = useQueryClient();
const { session } = useAuth();
const userId = session!.user.id;
return useMutation({
mutationFn: async ({ name, icon, color }) => {
const { error } = await supabase.from('habits').insert({
user_id: userId,
name,
icon,
color,
});
if (error) throw error;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['habits', userId] });
},
onError: (error) => {
Alert.alert('Error', error.message);
},
});
}Optimistiska uppdateringar för daglig avstämning
För den dagliga avstämningen (att markera en vana som utförd) använder du optimistiska uppdateringar för att uppdatera UI:t direkt innan nätverksbegäran är klar. När användaren trycker på knappen för avstämning uppdaterar du omedelbart cachen med det förväntade resultatet. Om begäran misslyckas återställer du cachen till det tidigare tillståndet. Då känns appen snabb även vid långsamma nätverk.
// Optimistic update pattern with useMutation
const toggleCompletion = useMutation({
mutationFn: (habitId) => insertCompletion(habitId, today),
onMutate: async (habitId) => {
// Cancel in-flight refetches
await queryClient.cancelQueries({ queryKey: ['completions', userId] });
// Save snapshot for rollback
const previous = queryClient.getQueryData(['completions', userId]);
// Optimistically add completion
queryClient.setQueryData(['completions', userId], (old) => [
...old,
{ habit_id: habitId, completed_date: today }
]);
return { previous }; // Pass snapshot to onError
},
onError: (_err, _habitId, context) => {
// Roll back on failure
queryClient.setQueryData(['completions', userId], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['completions', userId] });
},
});Nätverksstatus och offline-banner
Visa användarna en diskret offline-banner när enheten saknar internetanslutning, så att de förstår varför deras ändringar inte har synkroniserats. Använd @react-native-community/netinfo för att lyssna efter ändringar i anslutningen. React Query pausar automatiskt mutationer när enheten är offline och försöker igen när anslutningen återkommer — bannern gör helt enkelt detta beteende synligt för användaren.
import NetInfo from '@react-native-community/netinfo';
import { useEffect, useState } from 'react';
export function OfflineBanner() {
const [isOffline, setIsOffline] = useState(false);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
setIsOffline(!state.isConnected);
});
return unsubscribe;
}, []);
if (!isOffline) return null;
return (
<View style={{
backgroundColor: '#FF3B30',
padding: 8,
alignItems: 'center'
}}>
<Text style={{ color: 'white', fontWeight: 'bold' }}>
No internet connection — changes will sync when online
</Text>
</View>
);
}Paginering med FlatList och useInfiniteQuery
För flöden med många objekt implementerar du paginering med useInfiniteQuery. Den hämtar datasidor och lägger till dem i en löpande lista. Skicka fetchNextPage till FlatLists hanterare onEndReached så att nästa sida läses in automatiskt när användaren scrollar. Använd getNextPageParam för att härleda markören för nästa sida från svaret på den senaste sidan.
import { useInfiniteQuery } from '@tanstack/react-query';
export function useHabitsPaginated() {
return useInfiniteQuery({
queryKey: ['habits', 'paginated'],
queryFn: ({ pageParam = 0 }) =>
supabase.from('habits').select('*')
.range(pageParam, pageParam + 19), // 20 per page
getNextPageParam: (lastPage, allPages) => {
const nextOffset = allPages.length * 20;
return lastPage.data?.length === 20 ? nextOffset : undefined;
},
initialPageParam: 0,
});
}
// In screen:
const { data, fetchNextPage, hasNextPage } = useHabitsPaginated();
const habits = data?.pages.flatMap(p => p.data) ?? [];
<FlatList
data={habits}
onEndReached={() => hasNextPage && fetchNextPage()}
onEndReachedThreshold={0.3}
/>Animera listobjekt när de visas
Gör flödet mer dynamiskt genom att animera objekten när de visas. En enkel toning in och glidning uppåt vid montering med hjälp av Animated API ger en mer genomarbetad känsla utan tunga bibliotek. Skapa en återanvändbar wrapper, AnimatedListItem, som varje objekt passerar igenom. Animeringen körs en gång vid montering — använd useRef för att förhindra att den utlöses igen vid omrenderingar.
import { useRef, useEffect } from 'react';
import { Animated } from 'react-native';
export function AnimatedListItem({ children, index }) {
const opacity = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(20)).current;
useEffect(() => {
Animated.parallel([
Animated.timing(opacity, {
toValue: 1,
duration: 300,
delay: index * 50, // Stagger by index
useNativeDriver: true,
}),
Animated.timing(translateY, {
toValue: 0,
duration: 300,
delay: index * 50,
useNativeDriver: true,
}),
]).start();
}, []);
return (
<Animated.View style={{ opacity, transform: [{ translateY }] }}>
{children}
</Animated.View>
);
}Logik för beräkning av sviter
En svit är antalet sammanhängande dagar då en vana har utförts, fram till och med idag. Beräkna den i JavaScript från arrayen med utföranden. Sortera utförandena efter datum i fallande ordning, börja från idag och gå bakåt medan du räknar sammanhängande dagar. Detta är ren JavaScript-logik — inget nätverksanrop behövs om utförandena redan finns i React Query-cachen.
// src/utils/streaks.ts
export function calculateStreak(completedDates: string[]): number {
if (completedDates.length === 0) return 0;
const sorted = [...completedDates].sort().reverse();
const today = new Date().toISOString().split('T')[0];
let streak = 0;
let current = new Date(today);
for (const dateStr of sorted) {
const expected = current.toISOString().split('T')[0];
if (dateStr === expected) {
streak++;
current.setDate(current.getDate() - 1);
} else if (dateStr < expected) {
break; // Gap found
}
}
return streak;
}
// Usage:
const streak = calculateStreak(
completions
.filter(c => c.habit_id === habit.id)
.map(c => c.completed_date)
);Snabbtest
Testa dina kunskaper om koncepten inom React Native Mobile Development från den här lektionen.
Sammanfattning av lektionen
I den här lektionen har du lärt dig hur du konfigurerar React Query med beständig lagring för att tillhandahålla cachad data offline, hur du bygger anpassade hooks med useQuery och useMutation för CRUD-hantering av vanor samt hur du implementerar optimistiska uppdateringar för omedelbar återkoppling i UI:t. Du har också sett hur du lägger till paginering, animeringar när objekt visas och beräkning av sviter från cachad data. Härnäst finjusterar, testar och publicerar vi appen i båda butikerna.
Lär dig JavaScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 30
- Lektioner
- 120
Vanliga frågor
Är lektionen ”Kärnfunktion: dataflöde med offline-stöd” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen React Native Academy, inklusive ”Kärnfunktion: dataflöde med offline-stöd”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i React Native Academy innehåller totalt 4 lektioner.
Vad lär jag mig i ”Kärnfunktion: dataflöde med offline-stöd”?
Bygg det huvudsakliga dataflödet med React Query och beständig offline-lagring, implementera dra för att uppdatera och paginering, lägg till mutationer för att skapa och ta bort data och animera list… Ni övar på React Native Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig React Native Academy?
Du behöver inga förkunskaper. Utbildningen i React Native Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Kärnfunktion: dataflöde med offline-stöd”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här React Native Academy-lektionen?
Ja. Varje React Native Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Planera arkitektur och teknikstack
- Autentiseringsflöde och skyddade rutter
- Kärnfunktion: dataflöde med offline-stöd
- Finjustering, testning och publicering i båda butikerna