Modern JavaScript and TypeScript for React Native

ES modules, destructuring, optional chaining, async/await, and the TypeScript types and generics you need to keep a component tree honest.

Modules and safe property access

import React, { useCallback, useMemo } from 'react';
import { View, Text } from 'react-native';
import { formatDistance } from './utils/date';

const CONFIG = { apiBase: 'https://api.example.com', timeout: 15000 };

export function describe(order) {
  const { id, customer: { name, address } = {}, total = 0 } = order;
  const city = address?.city ?? 'unknown city';     // optional chaining plus fallback
  const label = order.items?.length ? 'multiple' : 'single';

  return { id, summary: name + ' in ' + city + ' (' + label + ', ' + total + ')' };
}

export async function loadOrders(signal) {
  const response = await fetch(CONFIG.apiBase + '/orders', { signal });
  if (!response.ok) throw new Error('HTTP ' + response.status);
  return response.json();
}

export const sum = (values) => values.reduce((a, b) => a + b, 0);
export default CONFIG;
  • ?.() calls a function only when it exists; ?? falls back only for null and undefined, unlike || which also replaces 0 and the empty string.
  • Named exports make refactoring safer in an editor; a default export is easy to rename inconsistently.
  • Use = {} defaults when destructuring nested objects, otherwise a missing parent throws.
  • Encode absolute imports in a @/ alias and in tsconfig paths so deep relative paths do not multiply.

Types that pay for themselves

export type OrderStatus = 'pending' | 'paid' | 'shipped' | 'cancelled';

export interface Customer {
  id: string;
  name: string;
  email?: string;
}

export interface Order<TItem = OrderLine> {
  id: string;
  status: OrderStatus;
  items: readonly TItem[];
  customer: Customer;
}

export interface OrderLine {
  sku: string;
  quantity: number;
  unitPrice: number;
}

export type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

export function total<T extends OrderLine>(lines: readonly T[]): number {
  return lines.reduce((sum, line) => sum + line.quantity * line.unitPrice, 0);
}

// create a union of the keys that exist on both shapes
export type Patch<T> = Partial<T> & { id: T extends { id: infer I } ? I : never };
TypeUse it forCommon mistake
unknownUntrusted input such as JSONUsing any and losing all checking
neverImpossible states and exhaustive switchesReturning it from a normal function
readonly T[]Props you must not mutatePassing it where a mutable array is required
Partial<T>Update payloadsAssuming absent means delete rather than unchanged

Hooks with dependency discipline

import { useEffect, useMemo, useRef, useState } from 'react';

export function useDebounced<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);       // cleanup runs before the next effect
  }, [value, delay]);

  return debounced;
}

export function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;                   // always the newest render's value
  return ref;
}

export function useFiltered<T>(items: readonly T[], query: string, key: (item: T) => string) {
  const normalized = query.trim().toLowerCase();
  return useMemo(
    () => (normalized ? items.filter((i) => key(i).toLowerCase().includes(normalized)) : items),
    [items, normalized, key],
  );
}
⚠️
A dependency array is a correctness contract, not an optimisation hint. Omitting a value the effect reads gives you a stale closure that only misbehaves after a re-render — the hardest class of React bug to reproduce.

FAQ

Should I use JavaScript or TypeScript for a new React Native app?
TypeScript. React Native's own APIs are typed, navigation params and Redux or Zustand stores become self-documenting, and most runtime crashes in a large app trace back to a value whose shape nobody checked.
Why does my <code>useEffect</code> run twice on mount?
React 18 in development intentionally double-invokes effects to surface missing cleanup. Write effects so running twice is harmless — that is what makes them correct in production too.

State management: Context, Redux Toolkit and Zustand Flexbox layout and responsive design

Last refreshed 2026-09-18.