Bridge 00B — React for React Native

Bridge ⏱ 3-5 giờ 📋 Prerequisites: đã chạy được app đầu tiên

Vai trò của trang này

Nếu đã vững React, có thể skim nhanh. Nếu mới học RN, trang này giúp hiểu React ở mức đủ để đọc Module 02 và Module 04 mà không bị ngợp.

🎯 Mục tiêu bài học

🧠 Kiến thức cốt lõi

1. Component là function nhận input và trả UI

Trong RN, UI không phải HTML. Component trả về các native primitives như View, Text, Pressable, TextInput. Nhưng mental model React vẫn giữ nguyên: props đi xuống, event đi lên, state làm UI render lại.

type UserCardProps = {
  name: string;
  role: string;
  onPress: () => void;
};

function UserCard({ name, role, onPress }: UserCardProps) {
  return (
    <Pressable onPress={onPress} style={styles.card}>
      <Text style={styles.name}>{name}</Text>
      <Text style={styles.role}>{role}</Text>
    </Pressable>
  );
}

2. Props là contract, không phải chỗ chứa logic lung tung

Props nên thể hiện component cần gì để render và emit event gì. Component con không nên tự biết quá nhiều về API, navigation, storage nếu không cần.

Không tốtTốt hơn
UserCard tự fetch user, tự navigate, tự format mọi thứ. Parent fetch data; UserCard nhận props và bắn onPress.
Props kiểu any. Props type rõ, optional có default hợp lý.

3. State là dữ liệu khiến UI thay đổi

Đừng đưa mọi thứ vào state. Chỉ đưa dữ liệu cần render lại UI hoặc cần giữ qua nhiều render.

const [query, setQuery] = useState('');
const [selectedId, setSelectedId] = useState<string | null>(null);

const filteredUsers = users.filter(user =>
  user.name.toLowerCase().includes(query.toLowerCase())
);

filteredUsers là derived data, không cần state riêng nếu tính toán nhẹ.

💻 Ví dụ code / ví dụ thực tế

Mini screen: Search user list

import { useMemo, useState } from 'react';
import { FlatList, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';

type User = {
  id: string;
  name: string;
  role: 'admin' | 'member';
};

const USERS: User[] = [
  { id: '1', name: 'Tien Dang', role: 'admin' },
  { id: '2', name: 'React Native Dev', role: 'member' },
  { id: '3', name: 'Mobile Engineer', role: 'member' },
];

export function UserListScreen() {
  const [query, setQuery] = useState('');
  const [selectedId, setSelectedId] = useState<string | null>(null);

  const users = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return USERS;
    return USERS.filter(user => user.name.toLowerCase().includes(q));
  }, [query]);

  return (
    <View style={styles.screen}>
      <TextInput
        value={query}
        onChangeText={setQuery}
        placeholder="Search user"
        style={styles.input}
      />

      <FlatList
        data={users}
        keyExtractor={item => item.id}
        renderItem={({ item }) => (
          <Pressable
            style={[styles.row, selectedId === item.id && styles.selectedRow]}
            onPress={() => setSelectedId(item.id)}
          >
            <Text style={styles.name}>{item.name}</Text>
            <Text style={styles.role}>{item.role}</Text>
          </Pressable>
        )}
        ListEmptyComponent={<Text style={styles.empty}>No users found</Text>}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, backgroundColor: '#fff' },
  input: {
    borderWidth: 1,
    borderColor: '#cbd5e1',
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 10,
    marginBottom: 12,
  },
  row: {
    padding: 14,
    borderWidth: 1,
    borderColor: '#e2e8f0',
    borderRadius: 8,
    marginBottom: 8,
  },
  selectedRow: { borderColor: '#2563eb', backgroundColor: '#eff6ff' },
  name: { fontWeight: '700', color: '#0f172a' },
  role: { color: '#64748b', marginTop: 4 },
  empty: { textAlign: 'center', color: '#64748b', marginTop: 24 },
});

🐞 Lỗi thường gặp

LỗiVì sao sai?Fix
Mutate state trực tiếp React không thấy reference đổi hoặc UI update khó đoán. Tạo object/array mới: setItems(prev => [...prev, item]).
Dùng index làm key cho list động Insert/delete/reorder làm React reuse nhầm row. Dùng id ổn định từ data.
Component quá to Fetch, form, list, modal, navigation dồn một chỗ. Tách container, presentational component, custom hook khi cần.
Derived data thành state riêng Dễ lệch giữa source state và derived state. Tính từ state gốc bằng biến thường hoặc useMemo khi nặng.

🏢 Case đi làm thật

Case: Form edit profile render sai sau khi save

Một bug phổ biến là screen có user từ API, copy sang formState, sau đó API trả user mới nhưng form vẫn giữ state cũ. Người mới thường fix bằng cách set state lung tung trong render. Cách đúng là xác định source of truth: form đang edit draft hay sync theo server? Nếu là draft, chỉ reset khi đổi user id hoặc khi user bấm discard.

🧪 Mini exercise thực tế

  1. Tạo UserListScreen như ví dụ.
  2. Thêm nút clear search.
  3. Thêm filter theo role admin/member.
  4. Cố tình dùng index làm key, insert một user vào đầu list, quan sát selected row có thể sai như thế nào.
  5. Tách UserRow thành component riêng với props rõ ràng.

⚡ Ghi nhớ nhanh

✅ Câu hỏi tự kiểm tra

  1. Props khác state như thế nào?
  2. Khi nào nên tách component?
  3. Vì sao không nên mutate array state?
  4. Vì sao index key có thể gây bug?
  5. Derived state là gì? Khi nào nó nguy hiểm?

🎤 Câu hỏi phỏng vấn

  1. How do props and state work in React?
  2. How would you structure a list screen in React Native?
  3. Why are stable keys important in React lists?
  4. What is the difference between controlled and uncontrolled input?
  5. How do you avoid over-engineering small components?