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 fornullandundefined, 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 intsconfigpaths 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 };| Type | Use it for | Common mistake |
|---|---|---|
unknown | Untrusted input such as JSON | Using any and losing all checking |
never | Impossible states and exhaustive switches | Returning it from a normal function |
readonly T[] | Props you must not mutate | Passing it where a mutable array is required |
Partial<T> | Update payloads | Assuming 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.
Related
State management: Context, Redux Toolkit and Zustand Flexbox layout and responsive design
Last refreshed 2026-09-18.