React Native Cheatsheet
State and Hooks
Use this React Native reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
useState
import { useState } from 'react'; const [count, setCount] = useState(0); const [user, setUser] = useState<User | null>(null); const [list, setList] = useState<string[]>([]); // Update based on previous value (always use for derived state) setCount(prev => prev + 1); // Partial object update setUser(prev => ({ ...prev!, name: 'Alice' })); // Array mutations setList(prev => [...prev, 'new item']); setList(prev => prev.filter(item => item !== 'old')); setList(prev => prev.map(item => item === 'old' ? 'new' : item)); // Lazy initializer (runs once) const [state, setState] = useState(() => expensiveInit());
useEffect
import { useEffect } from 'react'; // Runs after every render useEffect(() => { console.log('rendered'); }); // Runs once on mount useEffect(() => { fetchData(); }, []); // Runs when dep changes useEffect(() => { fetchUser(userId); }, [userId]); // Cleanup (runs before next effect + on unmount) useEffect(() => { const sub = subscribe(userId); return () => sub.unsubscribe(); }, [userId]);
Common RN patterns with useEffect
// AppState listener useEffect(() => { const sub = AppState.addEventListener('change', nextState => { if (nextState === 'active') refreshData(); }); return () => sub.remove(); }, []); // NetInfo listener useEffect(() => { const unsubscribe = NetInfo.addEventListener(state => { setIsOnline(state.isConnected ?? false); }); return unsubscribe; }, []); // Keyboard listeners useEffect(() => { const show = Keyboard.addListener('keyboardDidShow', e => { setKeyboardHeight(e.endCoordinates.height); }); const hide = Keyboard.addListener('keyboardDidHide', () => setKeyboardHeight(0)); return () => { show.remove(); hide.remove(); }; }, []);
useRef
import { useRef } from 'react'; // Component refs const inputRef = useRef<TextInput>(null); const listRef = useRef<FlatList>(null); inputRef.current?.focus(); inputRef.current?.blur(); inputRef.current?.clear(); listRef.current?.scrollToEnd({ animated: true }); // Mutable value that doesn't trigger re-render const timerRef = useRef<ReturnType<typeof setTimeout>>(); timerRef.current = setTimeout(() => {}, 1000); clearTimeout(timerRef.current); // Tracking previous value const prevCount = useRef(count); useEffect(() => { prevCount.current = count; }, [count]);
useCallback
import { useCallback } from 'react'; // Memoize function — stable reference across renders const handlePress = useCallback(() => { doSomething(id); }, [id]); // Critical for FlatList renderItem to prevent re-renders const renderItem = useCallback(({ item }) => ( <ItemCard item={item} onPress={handlePress} /> ), [handlePress]); <FlatList renderItem={renderItem} ... />
useMemo
import { useMemo } from 'react'; // Expensive computation const sortedList = useMemo( () => [...items].sort((a, b) => a.name.localeCompare(b.name)), [items] ); // Filtered data const filteredData = useMemo( () => data.filter(item => item.active && item.name.includes(query)), [data, query] );
useContext
import { createContext, useContext, useState } from 'react'; // Create const ThemeContext = createContext<'light' | 'dark'>('light'); // Provide export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light'); return ( <ThemeContext.Provider value={theme}> {children} </ThemeContext.Provider> ); } // Consume function MyComponent() { const theme = useContext(ThemeContext); return <View style={{ backgroundColor: theme === 'dark' ? '#000' : '#fff' }} />; }
useReducer
import { useReducer } from 'react'; type State = { count: number; loading: boolean }; type Action = | { type: 'increment' } | { type: 'decrement' } | { type: 'set_loading'; payload: boolean }; function reducer(state: State, action: Action): State { switch (action.type) { case 'increment': return { ...state, count: state.count + 1 }; case 'decrement': return { ...state, count: state.count - 1 }; case 'set_loading': return { ...state, loading: action.payload }; default: return state; } } const [state, dispatch] = useReducer(reducer, { count: 0, loading: false }); dispatch({ type: 'increment' }); dispatch({ type: 'set_loading', payload: true });
Custom Hooks
// useDebounce function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(handler); }, [value, delay]); return debouncedValue; } const debouncedSearch = useDebounce(searchText, 300);
// useAsyncData function useAsyncData<T>(fetcher: () => Promise<T>, deps: any[] = []) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { let cancelled = false; setLoading(true); fetcher() .then(result => { if (!cancelled) setData(result); }) .catch(err => { if (!cancelled) setError(err); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, deps); return { data, loading, error }; } const { data: users, loading } = useAsyncData(() => fetchUsers(), []);
// useAppState import { AppState, AppStateStatus } from 'react-native'; function useAppState() { const [appState, setAppState] = useState(AppState.currentState); useEffect(() => { const sub = AppState.addEventListener('change', setAppState); return () => sub.remove(); }, []); return appState; } const appState = useAppState(); // 'active' | 'background' | 'inactive'
// useKeyboard import { Keyboard, KeyboardEvent } from 'react-native'; function useKeyboard() { const [keyboardHeight, setKeyboardHeight] = useState(0); useEffect(() => { const show = Keyboard.addListener('keyboardWillShow', (e: KeyboardEvent) => { setKeyboardHeight(e.endCoordinates.height); }); const hide = Keyboard.addListener('keyboardWillHide', () => setKeyboardHeight(0)); return () => { show.remove(); hide.remove(); }; }, []); return keyboardHeight; }
React Query (TanStack Query) — Data Fetching
npm install @tanstack/react-query
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'; const queryClient = new QueryClient(); // Wrap app <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> // Query const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['users', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, // 5 min gcTime: 10 * 60 * 1000, // 10 min }); // Mutation const mutation = useMutation({ mutationFn: (newUser) => postUser(newUser), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }), }); mutation.mutate({ name: 'Alice' });
Zustand — Global State
npm install zustand
import { create } from 'zustand'; interface AppStore { count: number; user: User | null; increment: () => void; setUser: (user: User) => void; } const useStore = create<AppStore>((set) => ({ count: 0, user: null, increment: () => set(state => ({ count: state.count + 1 })), setUser: (user) => set({ user }), })); // In component const count = useStore(state => state.count); const increment = useStore(state => state.increment);
useAnimatedValue (Animated API)
import { Animated, useAnimatedValue } from 'react-native'; // RN 0.71+ hook — creates a stable Animated.Value const opacity = useAnimatedValue(0); const translateY = useAnimatedValue(20); // Pre-0.71 equivalent: // const opacity = useRef(new Animated.Value(0)).current; // Fade in Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true, }).start(); // Spring Animated.spring(translateY, { toValue: 0, tension: 50, friction: 7, useNativeDriver: true, }).start(({ finished }) => console.log('done')); // Sequence Animated.sequence([ Animated.timing(opacity, { toValue: 0, duration: 200, useNativeDriver: true }), Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }), ]).start(); // Parallel Animated.parallel([ Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true }), Animated.spring(translateY, { toValue: 0, useNativeDriver: true }), ]).start(); <Animated.View style={{ opacity, transform: [{ translateY }] }}> <Text>Animated</Text> </Animated.View>
useNativeDriver: trueoffloads animation to the native thread — always use it when possible. Limitation: onlyopacityandtransformare supported (nobackgroundColor,width,height).