Ydintoiminto: tietosyöte offline-tuella
Rakentakaa React Queryn avulla pääasiallinen tietosyöte offline-säilytyksellä, toteuttakaa vedä päivitettäväksi -toiminto ja sivutus, lisätkää luonti- ja poistomutaatiot sekä animoikaa luettelokohteiden ilmestyminen.
Ydintoiminto: tietosyöte offline-tuella on ilmainen React Native Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu React Native Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. React Native Academy-kurssilla on yhteensä 4 oppituntia.
Offline-first-suunnittelu
Offline-first-suunnittelu tarkoittaa, että sovellus toimii oikein ilman internetyhteyttä — käyttäjät voivat tarkastella välimuistissa olevia tietoja, tehdä muutoksia, jotka jonotetaan synkronoitaviksi, eikä verkkoyhteyden puuttuminen itsessään aiheuta virheilmoituksia. Mobiilikäyttäjät siirtyvät usein yhteyden ulottuville ja sen ulkopuolelle. Verkkoyhteyden katketessa rikkoutuva React Native -sovellus tuntuu epäluotettavalta, kun taas toimivana pysyvä sovellus tuntuu viimeistellyltä ja luotettavalta.
QueryClientin määrittäminen
React Queryn QueryClient muodostaa datakerroksen perustan. Määritä sille järkevät oletusarvot: staleTime, jolla vältetään tarpeettomat uudelleenhaut (esimerkiksi viisi minuuttia harvoin muuttuville tiedoille), sekä gcTime (roskienkeruun aika), joka säilyttää käyttämättömiä tietoja välimuistissa riittävän kauan, jotta niitä voidaan käyttää yhteyden palauduttua. Välitä tämä asiakas sovelluksen juuressa QueryClientProvider-komponentille.
// 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
},
},
});Query-välimuistin säilyttäminen AsyncStoragella
Jotta tiedot ovat käytettävissä offline-tilassa, säilytä React Queryn välimuisti AsyncStoragessa. Käytä @tanstack/react-query-persist-client-pakettia yhdessä AsyncStorage-persisterin kanssa. Sovelluksen käynnistyessä säilytetty välimuisti palautetaan ennen ensimmäistä verkkopyyntöä, joten käyttäjät näkevät tapaluettelonsa heti, vaikka haku ei olisi vielä valmis. Välimuisti ladataan asynkronisesti, joten näytä välimuistissa olevat tiedot optimistisesti ja päivitä näkymä, kun tuoreet tiedot saapuvat.
// 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>
);
}Tapaluettelon hakeminen useQuerylla
Luo mukautettu useHabits-hook, joka kapseloi Supabase-kyselyn useQuery-hookin sisään. Kyselyavain ['habits', userId] rajaa välimuistin nykyiseen käyttäjään ja estää tietojen vuotamisen tilien välillä. React Query palauttaa arvot data, isLoading ja error – välitä kaikki kolme näkymälle, jotta käyttöliittymä voi näyttää oikean tilan kussakin vaiheessa.
// 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,
});
}FlatListin hahmontaminen välimuistissa olevilla tiedoilla
Käytä useHabits-hookia HabitList-näkymässä. Näytä ensimmäisen latauksen aikana skeleton-latausanimaatio tai latausympyrä, käytä data-taulukkoa suoraan FlatListin lähteenä ja käsittele virhetila uudelleenyrityspainikkeella. Koska välimuisti säilytetään, data on seuraavilla sovelluksen käynnistyskerroilla heti käytettävissä ilman latausvälähdystä, samalla kun React Query hakee taustalla tuoreen version.
// 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}
/>
);
}Tapojen luominen useMutationilla
useMutation käsittelee lisäystoiminnon. Kutsu onSuccess-käsittelijässä queryClient.invalidateQueries(['habits', userId])-metodia käynnistääksesi tapaluettelon uuden haun. Näin juuri luotu tapa ilmestyy luetteloon heti luonnin jälkeen ilman välimuistin manuaalista päivittämistä. Aseta onError näyttämään käyttäjälle virheilmoitus, jos lisäys epäonnistuu.
// 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);
},
});
}Optimistiset päivitykset kuittausta varten
Käytä päivittäisessä kuittauksessa (tavan merkitsemisessä tehdyksi) optimistisia päivityksiä, jotta käyttöliittymä päivittyy heti ennen verkkopyynnön valmistumista. Kun käyttäjä napauttaa kuittauspainiketta, päivitä välimuisti välittömästi oletetulla tuloksella. Jos pyyntö epäonnistuu, palauta välimuisti edelliseen tilaansa. Näin sovellus tuntuu nopealta myös hitaissa verkoissa.
// 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] });
},
});Verkon tila ja offline-banneri
Näytä käyttäjille huomaamaton offline-banneri, kun laitteella ei ole internet-yhteyttä, jotta he ymmärtävät, miksi heidän muutoksensa eivät ole synkronoituneet. Kuuntele yhteyden muutoksia @react-native-community/netinfo-paketin avulla. React Query keskeyttää mutaatiot automaattisesti offline-tilassa ja yrittää niitä uudelleen yhteyden palautuessa – banneri tekee tästä toiminnasta käyttäjälle näkyvän.
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>
);
}Sivutus FlatListilla ja useInfiniteQuerylla
Toteuta paljon kohteita sisältävissä syötteissä sivutus käyttämällä useInfiniteQuery-hookia. Se hakee tietoja sivuittain ja lisää sivut kertyvään luetteloon. Välitä fetchNextPage FlatListin onEndReached-käsittelijälle, jotta seuraava sivu latautuu automaattisesti käyttäjän vierittäessä. Johda getNextPageParam-funktion avulla seuraavan sivun kursori viimeisen sivun vastauksesta.
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}
/>Luettelokohteiden sisääntulon animointi
Tee syötteestä dynaamisen tuntuinen animoimalla kohteet niiden ilmestyessä. Yksinkertainen kiinnityksen yhteydessä tehtävä häivytys ja liuku ylöspäin Animated API -rajapinnan avulla viimeistelee käyttöliittymää ilman raskaita kirjastoja. Luo uudelleenkäytettävä AnimatedListItem-kääre, jonka kautta jokainen kohde kulkee. Animaatio suoritetaan kerran kiinnityksen yhteydessä – estä sen käynnistyminen uudelleen uudelleenhahmonnusten aikana käyttämällä useRef-hookia.
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>
);
}Putken laskentalogiikka
Putki tarkoittaa peräkkäisten päivien määrää, joiden aikana tapa on tehty tähän päivään asti, tämä päivä mukaan lukien. Laske se JavaScriptillä completions-taulukosta. Järjestä suoritukset päivämäärän mukaan laskevaan järjestykseen, aloita tästä päivästä ja etene taaksepäin laskien peräkkäiset päivät. Tämä logiikka on puhdasta JavaScriptiä – verkkokutsua ei tarvita, jos suoritukset ovat jo React Queryn välimuistissa.
// 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)
);Pikatesti
Testaa tämän oppitunnin React Native Mobile Development -käsitteiden ymmärtämistä.
Oppitunnin kertaus
Tässä oppitunnissa opit: määrittämään React Queryn säilytyksen niin, että välimuistissa olevia tietoja voidaan käyttää offline-tilassa, rakentamaan mukautettuja hookeja useQueryn ja useMutationin avulla tapojen CRUD-toimintoja varten sekä toteuttamaan optimistisia päivityksiä käyttöliittymän välitöntä palautetta varten. Näit myös, miten välimuistissa oleviin tietoihin perustuvia sivutuksia, sisääntuloanimaatioita ja putken laskentaa lisätään. Seuraavaksi viimeistelemme, testaamme ja julkaisemme sovelluksen molemmissa kaupoissa.
Opi JavaScript tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 30
- Oppitunnit
- 120
Usein kysytyt kysymykset
Onko oppitunti ”Ydintoiminto: tietosyöte offline-tuella” ilmainen?
Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa React Native Academy-oppimispolun 3 oppituntia, myös oppitunnin “Ydintoiminto: tietosyöte offline-tuella”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. React Native Academy-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Ydintoiminto: tietosyöte offline-tuella”?
Rakentakaa React Queryn avulla pääasiallinen tietosyöte offline-säilytyksellä, toteuttakaa vedä päivitettäväksi -toiminto ja sivutus, lisätkää luonti- ja poistomutaatiot sekä animoikaa luettelokohtei… Harjoittelet React Native Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni React Native Academy-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin React Native Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.
Kuinka kauan ”Ydintoiminto: tietosyöte offline-tuella”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä React Native Academy-oppitunnilla?
Kyllä. Jokainen React Native Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Arkkitehtuurin ja teknologiapinon suunnittelu
- Todennusprosessi ja suojatut reitit
- Ydintoiminto: tietosyöte offline-tuella
- Viimeistely, testaus ja julkaisu molemmissa kaupoissa