React 18-এ useSyncExternalStore যুক্ত করা হয়েছে, যা এমন একটি হুক যা কম্পোনেন্টগুলোকে React ট্রির বাইরে থাকা যেকোনো সোর্স—যেমন browser APIs, WebSockets, বা third-party stores—থেকে ডেটা পড়তে সাহায্য করে। এটি useState এবং useEffect ব্যবহার করে ডেটা মিররিং করার পুরনো পদ্ধতিতে যে ভিজ্যুয়াল গ্লিচ (visual glitches) দেখা দিত, তা দূর করে। আপনি যদি কখনও দেখেন যে UI-এর একটি অংশ পুরনো উইন্ডো সাইজ দেখাচ্ছে কিন্তু অন্য অংশটি ইতিমধ্যে নতুন সাইজ দেখাচ্ছে (যার ফলে UI ফ্লিকার করে), তবে এই হুকটিই হলো আপনার কাঙ্ক্ষিত সমাধান।
কেন এক্সটার্নাল স্টেটের জন্য একটি নতুন হুক প্রয়োজন
React কম্পোনেন্টগুলো সবসময়ই তাদের নিজস্ব স্কোপের বাইরে থাকা বিষয়গুলোতে সাবস্ক্রাইব করতে পারত: যেমন window অবজেক্ট, একটি Redux store, বা একটি WebSocket connection। প্রচলিত পদ্ধতি ছিল useEffect-এর ভেতরে একটি সাবস্ক্রিপশন সেটআপ করা, setState-এর মাধ্যমে ইনকামিং ভ্যালুগুলোকে লোকাল স্টেটে পুশ করা এবং React-কে রি-রেন্ডার করতে দেওয়া। যখন React সিনক্রোনাসলি (synchronously) রেন্ডার করে তখন এটি ঠিকঠাক কাজ করে, কিন্তু React 18-এ concurrent rendering প্রবর্তন করা হয়েছে, যেখানে ব্রাউজার আসলে পেইন্ট (paint) করার আগে একটি কম্পোনেন্ট একাধিকবার রেন্ডার হতে পারে। এই মোডে “mirror-into-local-state” প্যাটার্নটি tearing তৈরি করতে পারে—অর্থাৎ একটি সিঙ্গেল রেন্ডার সাইকেলের মধ্যে UI-এর বিভিন্ন অংশ একই এক্সটার্নাল ভ্যালুর ভিন্ন ভিন্ন স্ন্যাপশট (snapshot) পড়তে পারে।
useSyncExternalStore তৈরি করা হয়েছে এই tearing রোধ করার জন্য। এটি এক্সটার্নাল সোর্স থেকে একটি snapshot (বর্তমান ভ্যালু) এবং একটি subscribe ফাংশন (কীভাবে পরিবর্তনের কথা জানানো হবে) চায়। কম্পোনেন্ট মাউন্ট (mount) হওয়ার সময় React subscribe কল করে এবং আনমাউন্ট (unmount) হওয়ার সময় স্বয়ংক্রিয়ভাবে আনসাবস্ক্রাইব করে। যখনই getSnapshot থেকে প্রাপ্ত স্ন্যাপশট পরিবর্তিত হয়, React এমন একটি রেন্ডার শিডিউল করে যা পুরো কম্পোনেন্ট ট্রির জন্য একই স্ন্যাপশট পড়ে, যা কনসিস্টেন্সি (consistency) নিশ্চিত করে।
প্রয়োজনীয় দুটি ফাংশন
| ফাংশন | এটি কী করে |
|---|---|
| subscribe | একটি কলব্যাক গ্রহণ করে যা এক্সটার্নাল ভ্যালু পরিবর্তনের সাথে সাথে কল করতে হবে। এটি একটি unsubscribe ফাংশন রিটার্ন করে যা React ক্লিনআপের সময় কল করবে। |
| getSnapshot | এক্সটার্নাল সোর্স থেকে বর্তমান ভ্যালু রিটার্ন করে। এটিকে অবশ্যই referentially stable হতে হবে—অর্থাৎ প্রতি রেন্ডারে একই ফাংশন অবজেক্ট হতে হবে—যাতে React সঠিকভাবে স্ন্যাপশটগুলো তুলনা করতে পারে। |
ব্রাউজারের 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() কল করবে, React কেবল তখনই এটিকে রি-রেন্ডার করবে যখন navigator.onLine প্রকৃতপক্ষে পরিবর্তিত হবে, এবং প্রতিটি রেন্ডারে একই ভ্যালু দেখা যাবে।
হুকটিকে সঠিকভাবে কাজ করানোর নিয়মাবলী
কম্পোনেন্টের প্রয়োজনীয়তম ক্ষুদ্রতম ডেটা রিটার্ন করুন।
যদি এক্সটার্নাল স্টোরে একটি বড় অবজেক্ট থাকে কিন্তু একটি কম্পোনেন্টের কেবল একটি ফিল্ডের প্রয়োজন হয়, তবে শুধু সেই ফিল্ডটিই রিটার্ন করুন। বড় ডেটা রিটার্ন করলে অপ্রয়োজনীয় রেন্ডার হতে পারে।getSnapshot-কে মেমোইজ (Memoise) করুন।
আপনি যদি প্রতি রেন্ডারে ফাংশনটি পুনরায় তৈরি করেন, তবে React এটিকে একটি নতুন সোর্স হিসেবে গণ্য করবে এবং এটি ইনফিনিট লুপে (infinite loop) পড়ে যেতে পারে। এটিকেuseCallback-এর মাধ্যমে র্যাপ করুন অথবা কম্পোনেন্টের বাইরে ডিফাইন করুন।প্রতিবার কল করার সময় নতুন অবজেক্ট রিটার্ন করা এড়িয়ে চলুন।
একটি নতুন অবজেক্ট রিটার্ন করলে (যেমন,{ count: store.getCount() }) প্রতি রেন্ডারে একটি নতুন রেফারেন্স তৈরি হয়, যা React-কে বোঝায় যে স্ন্যাপশট পরিবর্তিত হয়েছে এবং এটি একটি ইনফিনিট রেন্ডার লুপ তৈরি করে। প্রিমিতভ (primitives), স্ট্রিং, নম্বর বা মেমোইজড অবজেক্ট ব্যবহার করুন।অভ্যন্তরীণ কম্পোনেন্ট স্টেটের জন্য এটি ব্যবহার করবেন না।
যে ডেটা শুধুমাত্র কম্পোনেন্টের ভেতরে থাকে, তার জন্যuseState-ই সঠিক টুল।useSyncExternalStoreঅতিরিক্ত ওভারহেড (overhead) যোগ করে যা লোকাল স্টেটের জন্য প্রয়োজন নেই।
কখন এটি ব্যবহার করবেন
- Browser APIs – window size, media queries,
navigator.onLine, battery status. - Framework-agnostic stores – Zustand, Redux, MobX, বা যেকোনো কাস্টম স্টোর যা সাবস্ক্রিপশন API প্রদান করে।
- Mutable sources যা React-এর বাইরে আপডেট হয় – WebSocket messages, IndexedDB change events, Service Worker notifications.
যদি আপনার ডেটা সোর্স ইতিমধ্যে React-এর ভেতরে থাকে (যেমন, একটি প্যারেন্ট কম্পোনেন্টের স্টেট), তবে useState বা context ব্যবহার করাই শ্রেয়।
কমিউনিটি কী বলছে
যারা এটি প্রথম ব্যবহার শুরু করেছেন, তারা জানিয়েছেন যে useSyncExternalStore কনকারেন্ট-রেন্ডারিং সেটআপে উইন্ডো রিসাইজ করার সময় যে ফ্লিকার (flicker) দেখা যেত, তা দূর করে দেয়। কিছু লাইব্রেরি ইতিমধ্যে তাদের ইন্টারনাল হুকগুলোকে এই API-তে পরিবর্তন করেছে, যা React-এর বিভিন্ন ভার্সনে আরও প্রেডিক্টেবল (predictable) আচরণ নিশ্চিত করে। তবে এর একটি ছোট অসুবিধা হলো কিছুটা বাড়তি সতর্কতা অবলম্বন করতে হয়: ডেভেলপারদের ওই দুটি ফাংশনের স্ট্যাবিলিটি নিয়ে ভাবতে হবে এবং প্রতিবার কল করার সময় নতুন অবজেক্ট রিটার্ন করা এড়িয়ে চলতে হবে।
পরবর্তীতে যা খেয়াল রাখতে পারেন
React-এর আসন্ন রিলিজগুলোতে useSyncExternalStore-এর নিয়মাবলী আরও সুসংহত করা হতে পারে, যার ফলে সাধারণ ব্রাউজার API-গুলোর জন্য বিল্ট-ইন হেল্পার যুক্ত হওয়ার সম্ভাবনা রয়েছে। পুরনো useEffect-এর মতো অনুকরণ করা প্যাটার্নগুলো বাতিল করা হচ্ছে কি না, তা জানতে অফিসিয়াল React ব্লগের দিকে নজর রাখুন। ইতিমধ্যে, এই হুকটি স্থিতিশীল এবং পাবলিক API-এর অংশ, তাই আপনি নিশ্চিন্তে আপনার বিদ্যমান এক্সটার্নাল-স্টেট কোড রিফ্যাক্টর করতে পারেন।
দ্রুত পরীক্ষা করে দেখুন
উইন্ডো-সাইজ সিঙ্কিং, অনলাইন/অফলাইন ডিটেকশন এবং একটি সাধারণ Zustand স্টোর প্রদর্শনকারী একটি লাইভ ডেমো https://usesyncexternalstore.vercel.app/-তে রয়েছে। কমেন্টসহ সম্পূর্ণ সোর্স কোডটি https://github.com/dev48v/usesyncexternalstore-এ পাওয়া যাবে।
সারকথা: useSyncExternalStore React-কে তার রেন্ডার সাইকেলের বাইরের যেকোনো পরিবর্তনযোগ্য (mutable) ডেটার সাথে যুক্ত করার জন্য একটি নির্ভরযোগ্য সেতু প্রদান করে, যা টিয়ারিং (tearing) এবং অপ্রয়োজনীয় রেন্ডার প্রতিরোধ করে। এটি এক্সটার্নাল সোর্সগুলোতে প্রয়োগ করুন, স্ন্যাপশটটিকে ন্যূনতম এবং স্থিতিশীল রাখুন, আর ধারাবাহিকতা বজায় রাখার মূল কাজটি React-এর ওপর ছেড়ে দিন।
