Harness-engineering mobile-network-patterns

Mobile Network Patterns

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/mobile-network-patterns" ~/.claude/skills/intense-visions-harness-engineering-mobile-network-patterns && rm -rf "$T"
manifest: agents/skills/claude-code/mobile-network-patterns/SKILL.md
source content

Mobile Network Patterns

Handle network requests, offline support, caching, and connectivity monitoring in React Native

When to Use

  • Setting up API communication in a React Native app
  • Implementing offline-first or offline-tolerant behavior
  • Caching API responses for instant loading
  • Monitoring network connectivity and adapting UI
  • Implementing retry logic and optimistic updates

Instructions

  1. Use TanStack Query (React Query) for server state management. It handles caching, background refetching, retry logic, and offline support out of the box.
npm install @tanstack/react-query
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
      retry: 3,
      retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
    },
  },
});

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Navigation />
    </QueryClientProvider>
  );
}
  1. Create typed API hooks with TanStack Query.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function useOrders() {
  return useQuery({
    queryKey: ['orders'],
    queryFn: async (): Promise<Order[]> => {
      const response = await fetch(`${API_URL}/orders`);
      if (!response.ok) throw new Error('Failed to fetch orders');
      return response.json();
    },
  });
}

function useCreateOrder() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (input: CreateOrderInput) => {
      const response = await fetch(`${API_URL}/orders`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(input),
      });
      if (!response.ok) throw new Error('Failed to create order');
      return response.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['orders'] });
    },
  });
}
  1. Monitor network connectivity with
    @react-native-community/netinfo
    .
npx expo install @react-native-community/netinfo
import NetInfo from '@react-native-community/netinfo';
import { onlineManager } from '@tanstack/react-query';

// Integrate with TanStack Query — pauses queries when offline
onlineManager.setEventListener((setOnline) => {
  return NetInfo.addEventListener((state) => {
    setOnline(!!state.isConnected);
  });
});

// Use in components
function useNetworkStatus() {
  const [isConnected, setIsConnected] = useState(true);

  useEffect(() => {
    return NetInfo.addEventListener((state) => {
      setIsConnected(!!state.isConnected);
    });
  }, []);

  return isConnected;
}
  1. Persist query cache for offline-first behavior.
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import AsyncStorage from '@react-native-async-storage/async-storage';

const persister = createAsyncStoragePersister({
  storage: AsyncStorage,
  key: 'REACT_QUERY_CACHE',
});

export default function App() {
  return (
    <PersistQueryClientProvider
      client={queryClient}
      persistOptions={{ persister, maxAge: 24 * 60 * 60 * 1000 }}
    >
      <Navigation />
    </PersistQueryClientProvider>
  );
}
  1. Implement optimistic updates for responsive mutations.
function useToggleFavorite() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (productId: string) =>
      fetch(`${API_URL}/favorites/${productId}`, { method: 'POST' }),
    onMutate: async (productId) => {
      await queryClient.cancelQueries({ queryKey: ['product', productId] });
      const previous = queryClient.getQueryData<Product>(['product', productId]);

      queryClient.setQueryData<Product>(['product', productId], (old) =>
        old ? { ...old, isFavorite: !old.isFavorite } : old
      );

      return { previous };
    },
    onError: (_err, productId, context) => {
      queryClient.setQueryData(['product', productId], context?.previous);
    },
    onSettled: (_data, _err, productId) => {
      queryClient.invalidateQueries({ queryKey: ['product', productId] });
    },
  });
}
  1. Build an API client with interceptors for auth and error handling.
class ApiClient {
  private baseUrl: string;
  private getToken: () => Promise<string | null>;

  constructor(baseUrl: string, getToken: () => Promise<string | null>) {
    this.baseUrl = baseUrl;
    this.getToken = getToken;
  }

  async request<T>(path: string, options: RequestInit = {}): Promise<T> {
    const token = await this.getToken();
    const response = await fetch(`${this.baseUrl}${path}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
        ...options.headers,
      },
    });

    if (response.status === 401) {
      // Trigger token refresh or logout
      throw new AuthError('Session expired');
    }

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new ApiError(response.status, error.message ?? 'Request failed');
    }

    return response.json();
  }
}
  1. Show connectivity status in the UI.
function OfflineBanner() {
  const isConnected = useNetworkStatus();

  if (isConnected) return null;

  return (
    <View style={styles.banner}>
      <Text style={styles.bannerText}>You are offline. Some features may be limited.</Text>
    </View>
  );
}
  1. Implement request timeout for mobile networks. Mobile connections can hang without failing. Add explicit timeouts.
async function fetchWithTimeout(url: string, options: RequestInit = {}, timeout = 15000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);

  try {
    return await fetch(url, { ...options, signal: controller.signal });
  } finally {
    clearTimeout(timer);
  }
}

Details

Offline strategy spectrum:

  • Online-only: Show error when offline. Simplest, appropriate for real-time apps.
  • Cache-then-network: Show cached data immediately, fetch fresh data in background. Good for content apps.
  • Offline-first: Full local database with sync. Complex but provides the best UX for productivity apps.

TanStack Query network modes:

  • online
    (default): Queries pause when offline, resume when online
  • always
    : Queries run regardless of connectivity (for local data sources)
  • offlineFirst
    : Try cache first, then network

Retry strategies: Use exponential backoff with jitter for retries. Mobile networks are unreliable — aggressive retrying wastes battery and data. Default to 3 retries with

1s, 2s, 4s
delays.

Common mistakes:

  • Not handling network errors (fetch does not throw on 4xx/5xx — check
    response.ok
    )
  • Not setting request timeouts (default is infinite on React Native)
  • Caching user-specific data without clearing on logout
  • Not debouncing search/autocomplete requests (each keystroke fires a request)

Source

https://tanstack.com/query/latest

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.