Skip to content

React Best Practices

การกําหนดค่าเริ่มต้นให้กับ props แบบเดิมใช้ defaultProps ซึ่งกําลังจะถูก deprecate แบบใหม่ใช้ default parameters ที่เป็น JavaScript standard

❌ แบบเดิม

tsx
// ใช้ defaultProps ซึ่งกําลังจะถูก deprecate
interface Props {
  name?: string;
}

function Greeting({ name }: Props) {
  return <h1>Hello, {name}</h1>;
}

Greeting.defaultProps = {
  name: "Guest",
};

✅ แบบใหม่

tsx
// ใช้ default parameters
interface Props {
  name?: string;
}

function Greeting({ name = "Guest" }: Props) {
  return <h1>Hello, {name}</h1>;
}

Event Handling

การจัดการ event แบบเดิมมักสร้างฟังก์ชันใหม่ทุกครั้งที่ render แบบใหม่ใช้ useCallback เพื่อ memoize ฟังก์ชัน

❌ แบบเดิม

tsx
function Counter() {
  const [count, setCount] = useState(0);

  // สร้างฟังก์ชันใหม่ทุกครั้งที่ render
  const handleClick = () => {
    setCount(count + 1);
  };

  return <button onClick={handleClick}>Count: {count}</button>;
}

✅ แบบใหม่

tsx
function Counter() {
  const [count, setCount] = useState(0);

  // memoize ฟังก์ชันด้วย useCallback
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // ไม่มี dependencies เพราะใช้ updater function

  return <button onClick={handleClick}>Count: {count}</button>;
}

Side Effects

การจัดการ side effects แบบเดิมอาจทําให้เกิด memory leak แบบใหม่มีการ cleanup ที่เหมาะสม

❌ แบบเดิม

tsx
function UserStatus() {
  const [isOnline, setIsOnline] = useState(false);

  useEffect(() => {
    // ไม่มีการ cleanup อาจทําให้เกิด memory leak
    const timer = setInterval(() => {
      checkUserStatus();
    }, 1000);
  }, []);

  return <div>{isOnline ? "Online" : "Offline"}</div>;
}

✅ แบบใหม่

tsx
function UserStatus() {
  const [isOnline, setIsOnline] = useState(false);

  useEffect(() => {
    const timer = setInterval(() => {
      checkUserStatus();
    }, 1000);

    // cleanup function
    return () => clearInterval(timer);
  }, []);

  return <div>{isOnline ? "Online" : "Offline"}</div>;
}

Error Handling

การจัดการ error แบบเดิมใช้ try-catch ธรรมดา แบบใหม่ใช้ Error Boundary และ Suspense

❌ แบบเดิม

tsx
function UserProfile() {
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  useEffect(() => {
    try {
      const data = await fetchUserProfile();
      setData(data);
    } catch (err) {
      setError(err);
    }
  }, []);

  if (error) return <div>Error!</div>;
  if (!data) return <div>Loading...</div>;
  return <div>{data.name}</div>;
}

✅ แบบใหม่

tsx
// ErrorBoundary component
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <div>Something went wrong!</div>;
    }
    return this.props.children;
  }
}

// การใช้งาน
function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <UserProfile />
      </Suspense>
    </ErrorBoundary>
  );
}

Memoization

การ optimize performance แบบเดิมไม่ใช้ memoization แบบใหม่ใช้ useMemo และ memo เพื่อลดการ render ที่ไม่จําเป็น

❌ แบบเดิม

tsx
function ExpensiveList({ items }) {
  // คํานวณใหม่ทุกครั้งที่ component render
  const sortedItems = items.sort((a, b) => b - a);

  return (
    <ul>
      {sortedItems.map(item => <li key={item}>{item}</li>)}
    </ul>
  );
}

✅ แบบใหม่

tsx
// ใช้ useMemo สําหรับการคํานวณที่ซับซ้อน
const MemoizedList = memo(function ExpensiveList({ items }) {
  const sortedItems = useMemo(() => {
    return items.sort((a, b) => b - a);
  }, [items]);

  return (
    <ul>
      {sortedItems.map(item => <li key={item}>{item}</li>)}
    </ul>
  );
});

Custom Hooks

การจัดการ logic แบบเดิมเขียนรวมในคอมโพเนนต์ แบบใหม่แยกเป็น custom hook เพื่อนํากลับมาใช้ใหม่ได้

❌ แบบเดิม

tsx
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetchUsers()
      .then(data => setUsers(data))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}

✅ แบบใหม่

tsx
// แยก logic เป็น custom hook
function useUsers() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetchUsers()
      .then(data => setUsers(data))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, []);

  return { users, loading, error };
}

// การใช้งาน
function UserList() {
  const { users, loading, error } = useUsers();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}

Context Usage

การใช้ Context แบบเดิมอาจทําให้เกิดการ re-render ที่ไม่จําเป็น แบบใหม่แยก state และ dispatch เพื่อลดการ re-render

❌ แบบเดิม

tsx
const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  // ทุก component ที่ใช้ context จะ re-render เมื่อ theme เปลี่ยน
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

✅ แบบใหม่

tsx
const ThemeContext = createContext();
const ThemeDispatchContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  return (
    <ThemeContext.Provider value={theme}>
      <ThemeDispatchContext.Provider value={setTheme}>
        {children}
      </ThemeDispatchContext.Provider>
    </ThemeContext.Provider>
  );
}

// แยก consumer ตาม use case
function useTheme() {
  return useContext(ThemeContext);
}

function useThemeDispatch() {
  return useContext(ThemeDispatchContext);
}

Async Components

การโหลด component แบบ async แบบเดิมไม่มีการจัดการ loading state ที่ดี แบบใหม่ใช้ Suspense และ lazy

❌ แบบเดิม

tsx
const MyComponent = React.lazy(() => import("./MyComponent"));

function App() {
  return (
    <div>
      <MyComponent />
    </div>
  );
}

✅ แบบใหม่

tsx
const MyComponent = React.lazy(() => import("./MyComponent"));

function App() {
  return (
    <Suspense
      fallback={<Spinner />}
      // แสดง fallback หลังจาก 200ms
      unstable_expectedLoadTime={200}
    >
      <MyComponent />
    </Suspense>
  );
}

Form Handling

การจัดการฟอร์มแบบเดิมใช้ controlled components ทั้งหมด แบบใหม่ใช้ uncontrolled components กับ Form API เพื่อเพิ่ม performance

❌ แบบเดิม

tsx
// ทุก keystroke ทําให้เกิดการ re-render
function Form() {
  const [values, setValues] = useState({ name: "", email: "" });

  const handleChange = (e) => {
    setValues({ ...values, [e.target.name]: e.target.value });
  };

  return (
    <form>
      <input
        name="name"
        value={values.name}
        onChange={handleChange}
      />
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
      />
    </form>
  );
}

✅ แบบใหม่

tsx
// ใช้ uncontrolled components กับ Form API
function Form() {
  const formRef = useRef<HTMLFormElement>(null);

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    const formData = new FormData(formRef.current!);
    const values = Object.fromEntries(formData);
    // จัดการข้อมูลเมื่อ submit เท่านั้น
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input name="name" defaultValue="" />
      <input name="email" defaultValue="" />
    </form>
  );
}

State Updates

การอัพเดท state แบบเดิมอาจทําให้เกิด race condition แบบใหม่ใช้ updater function เพื่อป้องกันปัญหา

❌ แบบเดิม

tsx
function Counter() {
  const [count, setCount] = useState(0);

  // อาจเกิด race condition
  const increment = () => {
    setCount(count + 1);
    setCount(count + 1);
  };

  return <button onClick={increment}>{count}</button>;
}

✅ แบบใหม่

tsx
function Counter() {
  const [count, setCount] = useState(0);

  // ใช้ updater function ป้องกัน race condition
  const increment = () => {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  };

  return <button onClick={increment}>{count}</button>;
}

Component Composition

การส่งผ่าน props แบบเดิมอาจทําให้เกิด props drilling แบบใหม่ใช้ composition pattern

❌ แบบเดิม

tsx
// props drilling ผ่านหลาย component
function GrandParent({ user }) {
  return <Parent user={user} />;
}

function Parent({ user }) {
  return <Child user={user} />;
}

function Child({ user }) {
  return <div>{user.name}</div>;
}

✅ แบบใหม่

tsx
// ใช้ composition pattern
function UserInfo({ children }) {
  const user = useUser();
  return children(user);
}

function App() {
  return (
    <UserInfo>
      {user => <div>{user.name}</div>}
    </UserInfo>
  );
}