นากกายกำลังสับสวิตช์ระหว่างฉากแอปด้านหน้าและฉากพักเบื้องหลังบนโต๊ะสีมิ้นต์

React Native AppState: หยุดงานแพงเมื่อแอปพัก แล้ว sync ใหม่ตอนกลับมา

พี่โชว์Engineering

แอปที่ polling, เล่น animation หรือ refresh token ต่อแม้ผู้ใช้กลับหน้า Home เปลืองแบตและสร้าง race ตอนเปิดกลับมาครับ AppState บอกสถานะ foreground/background และส่ง event เมื่อสถานะเปลี่ยน เราจึงหยุดงานแพงเมื่อพักและ sync ใหม่เมื่อกลับมา active ได้

WHY: lifecycle ไม่ใช่ boolean เดียว

สถานะหลักคือ active กับ background; iOS ยังมี inactive ระหว่าง transition เช่นเปิด Notification Center ส่วน Android มี event focus/blur เพราะดึง notification drawer ลงอาจทำให้ผู้ใช้หยุดโต้ตอบทั้งที่ AppState ยังไม่เปลี่ยน

เหมือนร้านยังเปิดไฟอยู่ แต่พนักงานเดินไปหลังร้านครับ ป้าย “เปิด” อย่างเดียวจึงตอบไม่ได้ว่าควรเล่นวิดีโอหรือรับ gesture ตอนนี้หรือไม่ ต้องเลือก event ให้ตรงงาน

ตัวอย่างนี้เป็นโครงสร้าง mobile ที่ต้องตรวจบน simulator/device จริง จึงทำ static verification ของ syntax, imports และ API contract ตามเอกสาร

import { useEffect, useRef } from "react";
import { AppState, AppStateStatus } from "react-native";

export function useResumeSync(syncNow: () => Promise<void>) {
  const previous = useRef<AppStateStatus | null>(AppState.currentState);

  useEffect(() => {
    const subscription = AppState.addEventListener("change", (next) => {
      const wasAway = previous.current === "background" || previous.current === "inactive";
      previous.current = next;

      if (wasAway && next === "active") {
        void syncNow();
      }
    });

    return () => subscription.remove();
  }, [syncNow]);
}

จุดที่มักพลาด

ใน legacy architecture ค่า AppState.currentState อาจเป็น null ตอน launch จน native ส่งค่ากลับมา อย่าตีความ null ว่า background และอย่า sync ซ้ำทั้งจาก initial effect กับ event แรกโดยไม่มี idempotency

Listener ต้องถูกถอดตอน unmount และ callback ที่ส่งเข้า hook ควรมี reference เสถียร ไม่เช่นนั้น effect จะสมัครใหม่ทุก render งาน sync ต้องรับมือการกลับมา active หลายครั้ง, network ยังไม่พร้อม และผู้ใช้สลับแอปเร็ว ควร coalesce request และเก็บ timestamp/version แทนการยิงทุกครั้งแบบตาบอด

Recap

  • หยุด polling/animation ที่ไม่จำเป็นเมื่อ background
  • sync แบบ idempotent เมื่อกลับ active
  • อย่าตีความ currentState === null เป็นสถานะจริง
  • ทดสอบ Android focus/blur และ iOS inactive บนอุปกรณ์จริง