React 18-ൽ useSyncExternalStore എന്ന പുതിയ ഹുക്ക് ചേർത്തു. ഇത് React tree-ന് പുറത്തുള്ള സ്രോതസ്സുകളിൽ നിന്ന് (browser APIs, WebSockets, അല്ലെങ്കിൽ third-party stores) ഡാറ്റ വായിക്കാൻ സഹായിക്കുന്നു. useState, useEffect എന്നിവ ഉപയോഗിച്ച് ഡാറ്റ മിററിംഗ് (mirroring) ചെയ്യുമ്പോൾ ഉണ്ടാകാറുള്ള വിഷ്വൽ ഗ്ലിച്ചുകൾ (visual glitches) ഇത് ഒഴിവാക്കുന്നു. വിൻഡോ സൈസ് മാറുന്ന സമയത്ത് UI ഫ്ലിക്കർ (flicker) ചെയ്യുന്നത് കണ്ടിട്ടുണ്ടെങ്കിൽ, അതിനുള്ള പരിഹാരമാണ് ഈ ഹുക്ക്.

എന്തിനാണ് എക്സ്റ്റേണൽ സ്റ്റേറ്റിന് (external state) പുതിയൊരു ഹുക്ക് വേണ്ടിവരുന്നത്?

React കംപോണന്റുകൾക്ക് അവയുടെ പരിധിക്ക് പുറത്തുള്ള കാര്യങ്ങൾ സബ്‌സ്‌ക്രൈബ് ചെയ്യാൻ എപ്പോഴും സാധിച്ചിരുന്നു: window ഒബ്‌ജക്റ്റ്, ഒരു Redux സ്റ്റോർ, അല്ലെങ്കിൽ ഒരു WebSocket കണക്ഷൻ എന്നിവ ഇതിന് ഉദാഹരണങ്ങളാണ്. സാധാരണയായി useEffect-നുള്ളിൽ ഒരു സബ്‌സ്‌ക്രിപ്ഷൻ സെറ്റ് ചെയ്യുകയും, വരുന്ന വാല്യൂകൾ setState വഴി ലോക്കൽ സ്റ്റേറ്റിലേക്ക് മാറ്റുകയും, തുടർന്ന് React റീ-റെൻഡർ ചെയ്യുകയും ചെയ്യുന്ന രീതിയായിരുന്നു ഉപയോഗിച്ചിരുന്നത്. React സിൻക്രണസ് ആയി (synchronously) റെൻഡർ ചെയ്യുമ്പോൾ ഇത് കുഴപ്പമില്ലാതെ പ്രവർത്തിക്കും. എന്നാൽ React 18-ൽ 'concurrent rendering' അവതരിപ്പിച്ചു. ഇതിൽ ബ്രൗസർ യഥാർത്ഥത്തിൽ പെയിന്റ് ചെയ്യുന്നതിന് മുമ്പ് ഒരു കംപോണന്റ് പലതവണ റെൻഡർ ചെയ്യപ്പെട്ടേക്കാം. ഈ മോഡിൽ, "mirror-into-local-state" രീതി ഉപയോഗിക്കുന്നത് tearing എന്ന പ്രശ്നത്തിന് കാരണമായേക്കാം—അതായത് ഒരു സിംഗിൾ റെൻഡർ സൈക്കിളിനിടയിൽ UI-യുടെ വിവിധ ഭാഗങ്ങൾ ഒരേ എക്സ്റ്റേണൽ വാല്യൂവിന്റെ വ്യത്യസ്ത സ്നാപ്പ്ഷോട്ടുകൾ (snapshots) വായിക്കുന്നു.

Tearing ഒഴിവാക്കാനാണ് useSyncExternalStore നിർമ്മിച്ചിരിക്കുന്നത്. ഇത് എക്സ്റ്റേണൽ സ്രോതസ്സിൽ നിന്ന് ഒരു snapshot (നിലവിലെ വാല്യൂ), ഒരു subscribe ഫംഗ്ഷൻ (മാറ്റങ്ങൾ എങ്ങനെ അറിയാം എന്നത്) എന്നിവ ആവശ്യപ്പെടുന്നു. കംപോണന്റ് മൗണ്ട് (mount) ചെയ്യുമ്പോൾ React subscribe വിളിക്കുന്നു, മൗണ്ട് അൺമൗണ്ട് (unmount) ചെയ്യുമ്പോൾ തനിയെ അൺസബ്‌സ്‌ക്രൈബ് ചെയ്യുന്നു. getSnapshot നൽകുന്ന സ്നാപ്പ്ഷറ്റിൽ മാറ്റം വരുമ്പോൾ, കംപോണന്റ് ട്രീ മുഴുവനും ഒരേ സ്നാപ്പ്ഷോട്ട് വായിക്കുന്ന രീതിയിൽ React ഒരു റെൻഡർ ഷെഡ്യൂൾ ചെയ്യുന്നു, ഇത് ഡാറ്റാ കൺസിസ്റ്റൻസി (consistency) ഉറപ്പാക്കുന്നു.

ആവശ്യമായ രണ്ട് ഫംഗ്ഷനുകൾ

ഫംഗ്ഷൻ ഇത് എന്ത് ചെയ്യുന്നു
subscribe എക്സ്റ്റേണൽ വാല്യൂ മാറുമ്പോഴെല്ലാം വിളിക്കേണ്ട ഒരു callback സ്വീകരിക്കുന്നു. ക്ലീനപ്പ് (cleanup) സമയത്ത് React വിളിക്കുന്ന ഒരു unsubscribe ഫംഗ്ഷൻ ഇത് തിരികെ നൽകുന്നു.
getSnapshot എക്സ്റ്റേണൽ സ്രോതസ്സിൽ നിന്നുള്ള നിലവിലെ വാല്യൂ തിരികെ നൽകുന്നു. സ്നാപ്പ്ഷോട്ടുകൾ കൃത്യമായി താരതമ്യം ചെയ്യാൻ ഇത് referentially stable ആയിരിക്കണം—അതായത് ഓരോ റെൻഡറിലും ഒരേ ഫംഗ്ഷൻ ഒബ്‌ജക്റ്റ് തന്നെയായിരിക്കണം.

ബ്രൗസറിലെ online സ്റ്റാറ്റസിനായുള്ള ഒരു ലളിതമായ ഉദാഹരണം താഴെ നൽകുന്നു:

function useOnlineStatus() {
  return useSyncExternalStore(
    (onChange) => {
      window.addEventListener('online', onChange);
      window.addEventListener('offline', onChange);
      return () => {
        window.removeEventListener('online', onChange);
        window.removeEventListener('offline', onChange);
      };
    },
    () => navigator.onLine
  );
}

ഏതെങ്കിലും കംപോണന്റ് useOnlineStatus() വിളിക്കുമ്പോൾ, navigator.onLine എന്ന വാല്യൂ മാറുമ്പോൾ മാത്രമേ React അത് റീ-റെൻഡർ ചെയ്യുകയുള്ളൂ, കൂടാതെ ഓരോ റെൻഡറിലും ഒരേ വാല്യൂ തന്നെ കാണാനും സാധിക്കും.

ഹുക്ക് തകരാറിലാകാതിരിക്കാൻ ശ്രദ്ധിക്കേണ്ട കാര്യങ്ങൾ

  1. കംപോണന്റിന് ആവശ്യമുള്ള ഏറ്റവും ചെറിയ ഡാറ്റ മാത്രം നൽകുക.
    എക്സ്റ്റേണൽ സ്റ്റോറിൽ വലിയൊരു ഒബ്‌ജക്റ്റ് ഉണ്ടെങ്കിലും കംപോണന്റിന് ഒരു ഫീൽഡ് മാത്രമാണ് ആവശ്യമെങ്കിൽ, ആ ഫീൽഡ് മാത്രം നൽകുക. വലിയ ഡാറ്റകൾ നൽകുന്നത് അനാവശ്യമായ റീ-റെൻഡറുകൾക്ക് കാരണമാകും.

  2. getSnapshot മെമ്മൈസ് (Memoise) ചെയ്യുക.
    ഓരോ റെൻഡറിലും നിങ്ങൾ ഫംഗ്ഷൻ വീണ്ടും നിർമ്മിക്കുകയാണെങ്കിൽ, React അതിനെ പുതിയൊരു സ്രോതസ്സായി കണക്കാക്കുകയും ഇത് അനന്തമായ ലൂപ്പിന് (infinite loop) കാരണമാവുകയും ചെയ്തേക്കാം. ഇത് useCallback-ൽ ഉൾപ്പെടുത്തുകയോ കംപോണന്റിന് പുറത്ത് നിർവചിക്കുകയോ ചെയ്യുക.

  3. ഓരോ തവണയും പുതിയ ഒബ്‌ജക്റ്റുകൾ നൽകുന്നത് ഒഴിവാക്കുക.
    ഒരു പുതിയ ഒബ്‌ജക്റ്റ് നൽകുന്നത് (ഉദാഹരണത്തിന്, { count: store.getCount() }) ഓരോ റെൻഡറിലും പുതിയൊരു റഫറൻസ് സൃഷ്ടിക്കുന്നു. ഇത് സ്നാപ്പ്ഷറ്റ് മാറിയെന്ന് React-നെ തെറ്റിദ്ധരിപ്പിക്കുകയും അനന്തമായ റെൻഡർ ലൂപ്പിന് കാരണമാവുകയും ചെയ്യും. പ്രിമിറ്റീവ്സ് (primitives), സ്ട്രിംഗുകൾ, നമ്പറുകൾ അല്ലെങ്കിൽ മെമ്മൈസ് ചെയ്ത ഒബ്‌ജക്റ്റുകൾ എന്നിവ ഉപയോഗിക്കുക.

  4. കംപോണന്റിനുള്ളിലെ സ്റ്റേറ്റിനായി ഇത് ഉപയോഗിക്കരുത്.
    കംപോണന്റിനുള്ളിൽ മാത്രം നിലനിൽക്കുന്ന ഡാറ്റയ്ക്ക് useState തന്നെയാണ് ശരിയായ മാർഗ്ഗം. ലോക്കൽ സ്റ്റേറ്റിന് useSyncExternalStore ഉപയോഗിക്കുന്നത് അനാവശ്യമായ ഓവർഹെഡ് (overhead) ഉണ്ടാക്കും.

എപ്പോഴാണ് ഇത് ഉപയോഗിക്കേണ്ടത്?

  • Browser APIs – window size, media queries, navigator.onLine, battery status.
  • Framework-agnostic stores – Zustand, Redux, MobX, അല്ലെങ്കിൽ സബ്‌സ്‌ക്രിപ്ഷൻ API നൽകുന്ന ഏതെങ്കിലും കസ്റ്റം സ്റ്റോറുകൾ.
  • React-ന് പുറത്ത് അപ്‌ഡേറ്റ് ചെയ്യപ്പെടുന്ന Mutable sources – WebSocket സന്ദേശങ്ങൾ, IndexedDB ചേഞ്ച് ഇവന്റുകൾ, Service Worker നോട്ടിഫിക്കേഷനുകൾ.

നിങ്ങളുടെ ഡാറ്റാ സ്രോതസ്സ് ഇതിനകം തന്നെ React-നുള്ളിൽ ആണെങ്കിൽ (ഉദാഹരണത്തിന്, ഒരു പാരന്റ് കംപോണന്റിന്റെ സ്റ്റേറ്റ്), useState അല്ലെങ്കിൽ context ഉപയോഗിക്കുക.

കമ്മ്യൂണിറ്റി പറയുന്നത്

Concurrent-rendering ഉപയോഗിക്കുമ്പോൾ വിൻഡോ സൈസ് മാറ്റുന്ന സമയത്ത് കണ്ടിരുന്ന ഫ്ലിക്കർ (flicker) useSyncExternalStore ഒഴിവാക്കുന്നുണ്ടെന്ന് ആദ്യമായി ഉപയോഗിച്ചവർ പറയുന്നു. ചില ലൈബ്രറികൾ ഇതിനകം തന്നെ അവരുടെ ഇന്റേണൽ ഹുക്കുകൾ ഈ API-ലേക്ക് മാറ്റിയിട്ടുണ്ട്, ഇത് വിവിധ React വേർഷനുകളിൽ കൂടുതൽ കൃത്യമായ പ്രവർത്തനം വാഗ്ദാനം ചെയ്യുന്നു. ഇതിന്റെ ഒരു വെല്ലുവിളി (trade-off) ഡെവലപ്പർമാർക്ക് അല്പം കൂടുതൽ ശ്രദ്ധ ആവശ്യമാണ് എന്നതാണ്: ഈ രണ്ട് ഫംഗ്ഷനുകളുടെയും സ്റ്റെബിലിറ്റിയെക്കുറിച്ച് (stability) ചിന്തിക്കേണ്ടി വരും, കൂടാതെ ഓരോ തവണയും പുതിയ ഒബ്‌ജക്റ്റുകൾ നൽകുന്നത് ഒഴിവാക്കുകയും വേണം.

അടുത്തതായി ശ്രദ്ധിക്കേണ്ടവ

React’s upcoming releases may tighten the contract around useSyncExternalStore, possibly adding built-in helpers for common browser APIs. Keep an eye on the official React blog for any deprecations of the old useEffect-mirroring pattern. In the meantime, the hook is stable and part of the public API, so you can safely refactor existing external-state code.

Quick test drive

A live demo that showcases window-size syncing, online/offline detection, and a simple Zustand store lives at https://usesyncexternalstore.vercel.app/. The source code, complete with comments, is available at https://github.com/dev48v/usesyncexternalstore.

Takeaway: useSyncExternalStore gives React a reliable bridge to any mutable data that lives outside its render cycle, preventing tearing and unnecessary renders. Apply it to external sources, keep the snapshot minimal and stable, and let React do the heavy lifting of consistency.