உங்களுக்கு Effect தேவைப்படாமல் இருக்கலாம்

Effects என்பது React paradigm-இலிருந்து வெளியே செல்லும் escape hatch. அவை React-க்கு “வெளியே step” செய்து, non-React widget, network, அல்லது browser DOM போன்ற external system உடன் உங்கள் components-ஐ synchronize செய்ய அனுமதிக்கின்றன. External system ஏதும் இல்லையெனில் (உதாரணமாக, சில props அல்லது state மாறும் போது component state-ஐ update செய்ய விரும்பினால்), உங்களுக்கு Effect தேவைப்படக்கூடாது. தேவையற்ற Effects-ஐ remove செய்வது உங்கள் code-ஐ புரிந்துகொள்ள வசதியாக்கி, வேகமாக run செய்யவும், குறைவான errors-க்கும் உதவும்.

நீங்கள் கற்றுக்கொள்ள போவது

  • உங்கள் components-இலிருந்து தேவையற்ற Effects-ஐ ஏன் மற்றும் எப்படி remove செய்வது
  • Effects இல்லாமல் expensive computations-ஐ cache செய்வது எப்படி
  • Effects இல்லாமல் component state-ஐ reset மற்றும் adjust செய்வது எப்படி
  • Event handlers இடையில் logic-ஐ share செய்வது எப்படி
  • எந்த logic event handlers-க்கு நகர்த்தப்பட வேண்டும்
  • Changes பற்றி parent components-க்கு notify செய்வது எப்படி

தேவையற்ற Effects-ஐ remove செய்வது எப்படி

Effects தேவைப்படாத இரண்டு common cases உள்ளன:

  • Rendering-க்கான data-வை transform செய்ய Effects தேவையில்லை. உதாரணமாக, list-ஐ display செய்வதற்கு முன் filter செய்ய விரும்புகிறீர்கள் என்று வைத்துக் கொள்ளுங்கள். List மாறும்போது state variable-ஐ update செய்யும் Effect எழுத வேண்டும் என்று தோன்றலாம். ஆனால் இது inefficient. நீங்கள் state update செய்தால், screen-இல் என்ன இருக்க வேண்டும் என்பதை calculate செய்ய React முதலில் உங்கள் component functions-ஐ call செய்யும். பிறகு React இந்த changes-ஐ DOM-க்கு “commit” செய்து screen-ஐ update செய்யும். பிறகு React உங்கள் Effects-ஐ run செய்யும். உங்கள் Effect உடனடியாக state-ஐ update செய்தால், முழு process scratch-இலிருந்து மீண்டும் தொடங்கும்! தேவையற்ற render passes தவிர்க்க, உங்கள் components-ன் top level-இல் எல்லா data-வையும் transform செய்யுங்கள். உங்கள் props அல்லது state மாறும் போதெல்லாம் அந்த code automatically re-run ஆகும்.
  • User events handle செய்ய Effects தேவையில்லை. உதாரணமாக, பயனர் product வாங்கும்போது /api/buy POST request அனுப்பி notification காட்ட வேண்டும் என்று வைத்துக் கொள்ளுங்கள். Buy button click event handler-இல், என்ன நடந்தது என்று உங்களுக்கு துல்லியமாக தெரியும். Effect run ஆகும் நேரத்தில், பயனர் என்ன செய்தார் என்று தெரியாது (உதாரணமாக, எந்த button click செய்யப்பட்டது). அதனால்தான் பொதுவாக user events-ஐ corresponding event handlers-இல் handle செய்வீர்கள்.

External systems உடன் synchronize செய்ய Effects தேவை. உதாரணமாக, jQuery widget-ஐ React state உடன் synchronized ஆக வைத்திருக்கும் Effect எழுதலாம். Effects மூலம் data fetch செய்யவும் முடியும்: உதாரணமாக, current search query உடன் search results-ஐ synchronize செய்யலாம். Modern frameworks, components-இல் நேரடியாக Effects எழுதுவதை விட efficient ஆன built-in data fetching mechanisms provide செய்கின்றன என்பதை நினைவில் கொள்ளுங்கள்.

சரியான intuition பெற உதவ, சில common concrete examples-ஐப் பார்ப்போம்!

Props அல்லது state அடிப்படையில் state update செய்தல்

உங்களிடம் இரண்டு state variables கொண்ட component உள்ளது என்று வைத்துக் கொள்ளுங்கள்: firstName மற்றும் lastName. அவற்றை concatenate செய்து fullName calculate செய்ய விரும்புகிறீர்கள். மேலும், firstName அல்லது lastName மாறும் போதெல்லாம் fullName update ஆக வேண்டும். உங்கள் முதல் instinct fullName state variable சேர்த்து, அதை Effect-இல் update செய்வதாக இருக்கலாம்:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');

// 🔴 Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}

இது தேவையை விட complicated. மேலும் inefficient: fullName-க்கான stale value உடன் முழு render pass ஒன்றைச் செய்கிறது, பிறகு updated value உடன் உடனே re-render செய்கிறது. State variable மற்றும் Effect-ஐ remove செய்யுங்கள்:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Good: calculated during rendering
const fullName = firstName + ' ' + lastName;
// ...
}

ஏதேனும் ஒன்று existing props அல்லது state-இலிருந்து calculate செய்ய முடிந்தால், அதை state-இல் வைக்க வேண்டாம். அதற்கு பதிலாக, rendering போது calculate செய்யுங்கள். இது உங்கள் code-ஐ faster (extra “cascading” updates தவிர்க்கிறீர்கள்), simpler (சில code remove செய்கிறீர்கள்), மற்றும் குறைவான error-prone (வேறு state variables ஒன்றுடன் ஒன்று out of sync ஆகும் bugs தவிர்க்கிறீர்கள்) ஆக்கும். இந்த அணுகுமுறை உங்களுக்கு புதிதாக இருந்தால், state-இல் என்ன செல்ல வேண்டும் என்பதை Thinking in React விளக்குகிறது.

Expensive calculations-ஐ cache செய்தல்

இந்த component props மூலம் பெறும் todos-ஐ எடுத்து filter prop படி filter செய்து visibleTodos compute செய்கிறது. Result-ஐ state-இல் store செய்து Effect-இலிருந்து update செய்ய வேண்டும் என்று தோன்றலாம்:

function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');

// 🔴 Avoid: redundant state and unnecessary Effect
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);

// ...
}

முன்னைய example போல, இது தேவையற்றதும் inefficient-உம். முதலில் state மற்றும் Effect-ஐ remove செய்யுங்கள்:

function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ✅ This is fine if getFilteredTodos() is not slow.
const visibleTodos = getFilteredTodos(todos, filter);
// ...
}

பொதுவாக இந்த code சரி! ஆனால் getFilteredTodos() slow ஆக இருக்கலாம் அல்லது உங்களிடம் நிறைய todos இருக்கலாம். அப்படியானால் newTodo போன்ற தொடர்பில்லாத state variable மாறினால் getFilteredTodos()-ஐ recalculate செய்ய விரும்பமாட்டீர்கள்.

Expensive calculation-ஐ useMemo Hook-க்குள் wrap செய்வதன் மூலம் cache (அல்லது “memoize”) செய்யலாம்:

Note

React Compiler expensive calculations-ஐ உங்களுக்காக automatic ஆக memoize செய்ய முடியும்; பல cases-இல் manual useMemo தேவையை இது நீக்குகிறது.

import { useMemo, useState } from 'react';

function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = useMemo(() => {
// ✅ Does not re-run unless todos or filter change
return getFilteredTodos(todos, filter);
}, [todos, filter]);
// ...
}

Or, written as a single line:

import { useMemo, useState } from 'react';

function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ✅ Does not re-run getFilteredTodos() unless todos or filter change
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// ...
}

todos அல்லது filter மாறியிருந்தால் மட்டுமே inner function re-run ஆக வேண்டும் என்று இது React-க்கு சொல்கிறது. Initial render போது getFilteredTodos() return value-ஐ React நினைவில் வைத்துக்கொள்ளும். அடுத்த renders போது, todos அல்லது filter வேறுபட்டுள்ளதா என்று அது check செய்யும். Last time போலவே இருந்தால், useMemo store செய்த last result-ஐ return செய்யும். அவை வேறுபட்டால், React inner function-ஐ மீண்டும் call செய்து (அதன் result-ஐ store செய்து) return செய்யும்.

நீங்கள் useMemo-க்குள் wrap செய்யும் function rendering போது run ஆகும், எனவே இது pure calculations-க்கு மட்டும் வேலை செய்யும்.

Deep Dive

ஒரு calculation expensive என்பதை எப்படி அறிதல்?

பொதுவாக, நீங்கள் ஆயிரக்கணக்கான objects create செய்தாலோ அல்லது loop செய்தாலோ தவிர, அது expensive ஆக இருக்க வாய்ப்பு குறைவு. மேலும் நம்பிக்கை பெற, code-ன் ஒரு பகுதியில் செலவாகும் நேரத்தை அளக்க console log சேர்க்கலாம்:

console.time('filter array');
const visibleTodos = getFilteredTodos(todos, filter);
console.timeEnd('filter array');

நீங்கள் அளக்கும் interaction-ஐ செய்யுங்கள் (உதாரணமாக, input-இல் type செய்வது). பிறகு console-இல் filter array: 0.15ms போன்ற logs காண்பீர்கள். மொத்த logged time குறிப்பிடத்தக்க அளவாக சேர்ந்தால் (எ.கா. 1ms அல்லது அதற்கு மேல்), அந்த calculation-ஐ memoize செய்வது பொருத்தமாக இருக்கலாம். Experiment ஆக, அந்த interaction-க்கு total logged time குறைந்ததா என்பதை verify செய்ய calculation-ஐ useMemo-க்குள் wrap செய்யலாம்:

console.time('filter array');
const visibleTodos = useMemo(() => {
return getFilteredTodos(todos, filter); // todos மற்றும் filter மாறவில்லை என்றால் skip செய்யப்படும்
}, [todos, filter]);
console.timeEnd('filter array');

useMemo முதல் render-ஐ faster ஆக்காது. Updates போது தேவையற்ற work-ஐ skip செய்ய மட்டுமே இது உதவும்.

உங்கள் machine பயனர்களின் machines-ஐ விட faster ஆக இருக்கலாம் என்பதை நினைவில் கொள்ளுங்கள்; எனவே artificial slowdown உடன் performance test செய்வது நல்லது. உதாரணமாக, இதற்காக Chrome CPU Throttling option வழங்குகிறது.

Development-இல் performance measure செய்வது மிகச் சரியான results தராது என்பதையும் கவனிக்கவும். (உதாரணமாக, Strict Mode on ஆக இருந்தால், ஒவ்வொரு component-மும் ஒருமுறை அல்ல இருமுறை render ஆகும்.) மிகச் சரியான timings பெற, உங்கள் app-ஐ production-க்காக build செய்து, பயனர்கள் வைத்திருப்பதைப் போன்ற device-இல் test செய்யுங்கள்.

Prop மாறும் போது எல்லா state-ஐயும் reset செய்தல்

இந்த ProfilePage component userId prop பெறுகிறது. Page-இல் comment input உள்ளது; அதன் value-ஐ வைத்திருக்க comment state variable use செய்கிறீர்கள். ஒருநாள் problem கவனிக்கிறீர்கள்: ஒரு profile-இலிருந்து மற்றொன்றுக்கு navigate செய்யும்போது, comment state reset ஆகவில்லை. அதன் விளைவாக, தவறான user profile-இல் comment post செய்வது மேம்படுகிறது. Issue-ஐ fix செய்ய, userId மாறும் போதெல்லாம் comment state variable-ஐ clear செய்ய விரும்புகிறீர்கள்:

export default function ProfilePage({ userId }) {
const [comment, setComment] = useState('');

// 🔴 Avoid: Resetting state on prop change in an Effect
useEffect(() => {
setComment('');
}, [userId]);
// ...
}

இது inefficient, ஏனெனில் ProfilePage மற்றும் அதன் children முதலில் stale value உடன் render ஆகி, பிறகு மீண்டும் render ஆகும். மேலும் இது complicated, ஏனெனில் ProfilePage-க்குள் state கொண்ட ஒவ்வொரு component-இலும் இதைச் செய்ய வேண்டியிருக்கும். உதாரணமாக, comment UI nested ஆக இருந்தால், nested comment state-ஐயும் clear செய்ய விரும்புவீர்கள்.

அதற்கு பதிலாக, ஒவ்வொரு user’s profile-உம் conceptually different profile என்று React-க்கு explicit key கொடுத்து சொல்லலாம். உங்கள் component-ஐ இரண்டாக split செய்து, outer component-இலிருந்து inner one-க்கு key attribute pass செய்யுங்கள்:

export default function ProfilePage({ userId }) {
return (
<Profile
userId={userId}
key={userId}
/>
);
}

function Profile({ userId }) {
// ✅ This and any other state below will reset on key change automatically
const [comment, setComment] = useState('');
// ...
}

பொதுவாக, அதே component அதே இடத்தில் render செய்யப்பட்டால் React state-ஐ preserve செய்கிறது. Profile component-க்கு userId-ஐ key ஆக pass செய்வதன் மூலம், வேறுபட்ட userId கொண்ட இரண்டு Profile components state share செய்யக்கூடாத இரண்டு different components ஆக நடத்த React-க்கு கேட்கிறீர்கள். Key (நீங்கள் userId ஆக set செய்தது) மாறும் ஒவ்வொரு முறையும், React DOM-ஐ recreate செய்து, Profile component மற்றும் அதன் children அனைத்தின் state-ஐ reset செய்யும். இப்போது profiles இடையில் navigate செய்யும்போது comment field automatic ஆக clear ஆகும்.

இந்த example-இல், outer ProfilePage component மட்டுமே export செய்யப்பட்டு project-இல் உள்ள பிற files-க்கு visible ஆகிறது என்பதை கவனிக்கவும். ProfilePage render செய்யும் components அதற்கு key pass செய்ய வேண்டியதில்லை: அவை userId-ஐ regular prop ஆக pass செய்கின்றன. ProfilePage அதை inner Profile component-க்கு key ஆக pass செய்வது implementation detail.

Prop மாறும் போது சில state-ஐ adjust செய்தல்

சில நேரங்களில், prop change போது state-ன் ஒரு பகுதியை reset அல்லது adjust செய்ய விரும்பலாம், ஆனால் முழுவதையும் அல்ல.

இந்த List component items list-ஐ prop ஆக பெறுகிறது, மேலும் selected item-ஐ selection state variable-இல் maintain செய்கிறது. items prop வேறு array பெறும் ஒவ்வொரு முறையும் selection-ஐ null ஆக reset செய்ய விரும்புகிறீர்கள்:

function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);

// 🔴 Avoid: Adjusting state on prop change in an Effect
useEffect(() => {
setSelection(null);
}, [items]);
// ...
}

இதுவும் ideal அல்ல. items மாறும் ஒவ்வொரு முறையும், List மற்றும் அதன் child components முதலில் stale selection value உடன் render ஆகும். பிறகு React DOM-ஐ update செய்து Effects-ஐ run செய்யும். இறுதியில், setSelection(null) call List மற்றும் அதன் child components-க்கு மற்றொரு re-render ஏற்படுத்தி, இந்த முழு process-ஐ மீண்டும் தொடங்கும்.

Effect-ஐ delete செய்வதிலிருந்து தொடங்குங்கள். அதற்கு பதிலாக, rendering போது state-ஐ நேரடியாக adjust செய்யுங்கள்:

function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);

// Better: Adjust the state while rendering
const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) {
setPrevItems(items);
setSelection(null);
}
// ...
}

இப்படியாக previous renders-இலிருந்து information store செய்தல் புரிந்துகொள்ள கடினமாக இருக்கலாம், ஆனால் அதே state-ஐ Effect-இல் update செய்வதை விட இது சிறந்தது. மேலுள்ள example-இல், setSelection render போது நேரடியாக call செய்யப்படுகிறது. return statement உடன் அது வெளியேறியவுடன் React List-ஐ உடனடியாக re-render செய்யும். React இன்னும் List children-ஐ render செய்யவோ DOM-ஐ update செய்யவோ இல்லை, எனவே stale selection value-ஐ render செய்வதை List children skip செய்ய இது அனுமதிக்கிறது.

Rendering போது component-ஐ update செய்தால், React returned JSX-ஐ throw away செய்து உடனே rendering retry செய்யும். மிகவும் slow cascading retries தவிர்க்க, render போது அதே component-ன் state-ஐ மட்டுமே update செய்ய React அனுமதிக்கிறது. Render போது மற்றொரு component-ன் state-ஐ update செய்தால், error காண்பீர்கள். Loops தவிர்க்க items !== prevItems போன்ற condition அவசியம். State-ஐ இவ்வாறு adjust செய்யலாம், ஆனால் மற்ற side effects (DOM change செய்தல் அல்லது timeouts set செய்தல் போன்றவை) components pure ஆக இருக்க event handlers அல்லது Effects-இல் இருக்க வேண்டும்.

இந்த pattern Effect-ஐ விட efficient என்றாலும், பெரும்பாலான components-க்கு இதுவும் தேவையில்லை. எப்படி செய்தாலும், props அல்லது மற்ற state அடிப்படையில் state adjust செய்வது உங்கள் data flow-ஐ புரிந்துகொள்ளவும் debug செய்யவும் கடினமாக்குகிறது. அதற்கு பதிலாக key மூலம் எல்லா state-ஐ reset செய்ய முடியுமா அல்லது rendering போது எல்லாவற்றையும் calculate செய்ய முடியுமா என்று எப்போதும் check செய்யுங்கள். உதாரணமாக, selected item-ஐ store (மற்றும் reset) செய்வதற்கு பதிலாக, selected item ID-ஐ store செய்யலாம்:

function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selectedId, setSelectedId] = useState(null);
// ✅ Best: Calculate everything during rendering
const selection = items.find(item => item.id === selectedId) ?? null;
// ...
}

இப்போது state-ஐ “adjust” செய்யவே தேவையில்லை. Selected ID உடைய item list-இல் இருந்தால், அது selected ஆகவே இருக்கும். இல்லையெனில், matching item கிடைக்காததால் rendering போது calculated selection null ஆகும். இந்த behavior வேறுபட்டது, ஆனால் arguably சிறந்தது; ஏனெனில் items-க்கு பெரும்பாலான changes selection-ஐ preserve செய்யும்.

Event handlers இடையே logic பகிர்தல்

ஒரு product page-இல் இரண்டு buttons (Buy மற்றும் Checkout) உள்ளதாக வைத்துக்கொள்ளுங்கள்; இரண்டும் அந்த product-ஐ வாங்க அனுமதிக்கின்றன. User product-ஐ cart-இல் சேர்க்கும் ஒவ்வொரு முறையும் notification காட்ட விரும்புகிறீர்கள். இரண்டு buttons-ன் click handlers-இலுமே showNotification() call செய்வது repetitive ஆக தோன்றும்; எனவே இந்த logic-ஐ Effect-இல் வைக்க நீங்கள் tempted ஆகலாம்:

function ProductPage({ product, addToCart }) {
// 🔴 Avoid: Event-specific logic inside an Effect
useEffect(() => {
if (product.isInCart) {
showNotification(`${product.name} shopping cart-இல் சேர்க்கப்பட்டது!`);
}
}, [product]);

function handleBuyClick() {
addToCart(product);
}

function handleCheckoutClick() {
addToCart(product);
navigateTo('/checkout');
}
// ...
}

இந்த Effect தேவையற்றது. பெரும்பாலும் இது bugs-ஐயும் ஏற்படுத்தும். உதாரணமாக, page reloads இடையிலும் உங்கள் app shopping cart-ஐ “நினைவில்” வைத்திருக்கிறது என வைத்துக்கொள்ளுங்கள். Product-ஐ cart-இல் ஒருமுறை சேர்த்து page-ஐ refresh செய்தால், notification மீண்டும் தோன்றும். அந்த product page-ஐ refresh செய்யும் ஒவ்வொரு முறையும் அது தொடர்ந்து தோன்றும். காரணம், page load ஆகும்போதே product.isInCart ஏற்கனவே true ஆக இருக்கும்; எனவே மேலுள்ள Effect showNotification()-ஐ call செய்யும்.

சில code Effect-இலா அல்லது event handler-இலா இருக்க வேண்டும் என்பது தெளிவாக இல்லையெனில், இந்த code ஏன் run ஆக வேண்டும் என்று உங்களையே கேளுங்கள். Component user-க்கு காட்டப்பட்டது என்பதற்காக run ஆக வேண்டிய code-க்கு மட்டும் Effects-ஐ use செய்யுங்கள். இந்த example-இல், page காட்டப்பட்டது என்பதற்காக அல்ல; user button-ஐ அழுத்தியதால் notification தோன்ற வேண்டும்! Effect-ஐ delete செய்து, shared logic-ஐ இரு event handlers-இலிருந்தும் call செய்யப்படும் ஒரு function-க்குள் வையுங்கள்:

function ProductPage({ product, addToCart }) {
// ✅ Good: Event-specific logic is called from event handlers
function buyProduct() {
addToCart(product);
showNotification(`${product.name} shopping cart-இல் சேர்க்கப்பட்டது!`);
}

function handleBuyClick() {
buyProduct();
}

function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}

இது தேவையற்ற Effect-ஐ remove செய்வதுடன் bug-ஐயும் fix செய்கிறது.

POST request அனுப்புதல்

இந்த Form component இரண்டு வகையான POST requests அனுப்புகிறது. அது mount ஆகும்போது ஒரு analytics event அனுப்புகிறது. Form-ஐ நிரப்பி Submit button-ஐ click செய்தால், அது /api/register endpoint-க்கு POST request அனுப்பும்:

function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');

// ✅ Good: This logic should run because the component was displayed
useEffect(() => {
post('/analytics/event', { eventName: 'visit_form' });
}, []);

// 🔴 Avoid: Event-specific logic inside an Effect
const [jsonToSubmit, setJsonToSubmit] = useState(null);
useEffect(() => {
if (jsonToSubmit !== null) {
post('/api/register', jsonToSubmit);
}
}, [jsonToSubmit]);

function handleSubmit(e) {
e.preventDefault();
setJsonToSubmit({ firstName, lastName });
}
// ...
}

முந்தைய example-இல் இருந்த அதே criteria-ஐ இங்கே apply செய்வோம்.

Analytics POST request Effect-இலேயே இருக்க வேண்டும். ஏனெனில் analytics event-ஐ அனுப்புவதற்கான காரணம் form காட்டப்பட்டது என்பதே. (Development-இல் அது இருமுறை fire ஆகும்; அதை எப்படி கையாளுவது என்பதை இங்கே பார்க்கவும்.)

ஆனால் /api/register POST request form காட்டப்பட்டதால் ஏற்படுவது அல்ல. நீங்கள் request-ஐ ஒரு குறிப்பிட்ட தருணத்தில் மட்டும் அனுப்ப விரும்புகிறீர்கள்: user button-ஐ அழுத்தும்போது. அது அந்த குறிப்பிட்ட interaction-இல் மட்டுமே நடக்க வேண்டும். இரண்டாவது Effect-ஐ delete செய்து, அந்த POST request-ஐ event handler-க்குள் move செய்யுங்கள்:

function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');

// ✅ Good: This logic runs because the component was displayed
useEffect(() => {
post('/analytics/event', { eventName: 'visit_form' });
}, []);

function handleSubmit(e) {
e.preventDefault();
// ✅ Good: Event-specific logic is in the event handler
post('/api/register', { firstName, lastName });
}
// ...
}

சில logic-ஐ event handler-இலா அல்லது Effect-இலா வைக்க வேண்டும் என்று தீர்மானிக்கும்போது, user-ன் perspective-இல் அது எந்த வகையான logic என்பதே நீங்கள் பதிலளிக்க வேண்டிய முக்கிய கேள்வி. இந்த logic ஒரு குறிப்பிட்ட interaction காரணமாக ஏற்பட்டால், அதை event handler-இல் வைத்திருங்கள். User screen-இல் component-ஐ பார்ப்பதால் அது ஏற்படுமானால், அதை Effect-இல் வைத்திருங்கள்.

Computations-ஐ chain செய்தல்

சில நேரங்களில், ஒரு state-ன் ஒரு பகுதியை மற்ற state அடிப்படையில் adjust செய்யும் Effects-ஐ chain செய்ய உங்களுக்கு தோன்றலாம்:

function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);

// 🔴 Avoid: Chains of Effects that adjust the state solely to trigger each other
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);

useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1)
setGoldCardCount(0);
}
}, [goldCardCount]);

useEffect(() => {
if (round > 5) {
setIsGameOver(true);
}
}, [round]);

useEffect(() => {
alert('நல்ல game!');
}, [isGameOver]);

function handlePlaceCard(nextCard) {
if (isGameOver) {
throw Error('Game ஏற்கனவே முடிந்துவிட்டது.');
} else {
setCard(nextCard);
}
}

// ...

இந்த code-இல் இரண்டு பிரச்சினைகள் உள்ளன.

முதல் பிரச்சினை, இது மிகவும் inefficient: chain-இல் உள்ள ஒவ்வொரு set call-க்கும் இடையில் component (மற்றும் அதன் children) re-render ஆக வேண்டும். மேலுள்ள example-இல், worst case-இல் (setCard → render → setGoldCardCount → render → setRound → render → setIsGameOver → render) கீழுள்ள tree-க்கு மூன்று தேவையற்ற re-renders ஏற்படும்.

இரண்டாவது பிரச்சினை, அது slow அல்லாதிருந்தாலும், உங்கள் code evolve ஆகும்போது, நீங்கள் எழுதிய “chain” புதிய requirements-க்கு பொருந்தாத cases-ஐ சந்திப்பீர்கள். Game moves history-ஐ step through செய்ய ஒரு வழி சேர்க்கிறீர்கள் என கற்பனை செய்யுங்கள். அதற்காக ஒவ்வொரு state variable-ஐயும் கடந்தகால value ஒன்றாக update செய்வீர்கள். ஆனால் card state-ஐ கடந்தகால value-ஆக set செய்தால் Effect chain மீண்டும் trigger ஆகி, நீங்கள் காட்டும் data-ஐ மாற்றிவிடும். இத்தகைய code பெரும்பாலும் rigid மற்றும் fragile ஆக இருக்கும்.

இந்த case-இல், rendering போது calculate செய்யக்கூடியதை calculate செய்து, state-ஐ event handler-இல் adjust செய்வது சிறந்தது:

function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);

// ✅ Calculate what you can during rendering
const isGameOver = round > 5;

function handlePlaceCard(nextCard) {
if (isGameOver) {
throw Error('Game ஏற்கனவே முடிந்துவிட்டது.');
}

// ✅ Calculate all the next state in the event handler
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) {
alert('நல்ல game!');
}
}
}
}

// ...

இது மிகவும் efficient. மேலும், game history-ஐ view செய்ய ஒரு வழி implement செய்தால், இப்போது ஒவ்வொரு state variable-ஐயும் கடந்தகால move-ஆக set செய்ய முடியும்; மற்ற ஒவ்வொரு value-யையும் adjust செய்யும் Effect chain trigger ஆகாது. பல event handlers இடையே logic-ஐ reuse செய்ய வேண்டுமெனில், நீங்கள் ஒரு function extract செய்து அந்த handlers-இலிருந்து அதை call செய்யலாம்.

Event handlers-க்குள் state snapshot போல behave செய்யும் என்பதை நினைவில் கொள்ளுங்கள். உதாரணமாக, setRound(round + 1) call செய்த பிறகும், round variable user button click செய்த நேரத்தில் இருந்த value-ஐயே பிரதிபலிக்கும். Calculations-க்கு அடுத்த value தேவைப்பட்டால், அதை const nextRound = round + 1 போல manual ஆக define செய்யுங்கள்.

சில cases-இல், அடுத்த state-ஐ event handler-இல் நேரடியாக calculate செய்ய முடியாது. உதாரணமாக, பல dropdowns உள்ள form ஒன்றை கற்பனை செய்யுங்கள்; அடுத்த dropdown-ன் options முந்தைய dropdown-இல் selected value-ஐ சார்ந்திருக்கின்றன. அப்போது நீங்கள் network உடன் synchronize செய்வதால் Effects chain பொருத்தமானது.

Application-ஐ initialize செய்தல்

சில logic app load ஆகும்போது ஒருமுறை மட்டுமே run ஆக வேண்டும்.

அதை top-level component-இல் உள்ள Effect-இல் வைக்க நீங்கள் tempted ஆகலாம்:

function App() {
// 🔴 Avoid: Effects with logic that should only ever run once
useEffect(() => {
loadDataFromLocalStorage();
checkAuthToken();
}, []);
// ...
}

ஆனால் அது development-இல் இருமுறை run ஆகும் என்பதை நீங்கள் விரைவில் கவனிப்பீர்கள். இது issues ஏற்படுத்தலாம்; உதாரணமாக, function இருமுறை call செய்ய வடிவமைக்கப்படாததால் authentication token invalid ஆகக்கூடும். பொதுவாக, உங்கள் components remount செய்யப்படும்போது resilient ஆக இருக்க வேண்டும். இதில் உங்கள் top-level App component-உம் அடங்கும்.

Production-இல் நடைமுறையில் அது ஒருபோதும் remount ஆகாமல் இருக்கலாம்; ஆனால் எல்லா components-இலும் அதே constraints-ஐ பின்பற்றுவது code-ஐ move செய்யவும் reuse செய்யவும் உதவும். சில logic ஒவ்வொரு component mount-க்கும் ஒருமுறை அல்ல, ஒவ்வொரு app load-க்கும் ஒருமுறை மட்டுமே run ஆக வேண்டும் என்றால், அது ஏற்கனவே execute ஆனதா என்று track செய்ய top-level variable சேர்க்கவும்:

let didInit = false;

function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
// ✅ Only runs once per app load
loadDataFromLocalStorage();
checkAuthToken();
}
}, []);
// ...
}

Module initialization போது, app render ஆகும் முன்பும் அதை run செய்யலாம்:

if (typeof window !== 'undefined') { // browser-இல் run ஆகிறோமா என்று check செய்க.
// ✅ Only runs once per app load
checkAuthToken();
loadDataFromLocalStorage();
}

function App() {
// ...
}

Top level-இல் உள்ள code உங்கள் component import செய்யப்படும் போது ஒருமுறை run ஆகும்; அது இறுதியில் render ஆகவில்லை என்றாலும். Arbitrary components import செய்யும்போது slowdown அல்லது எதிர்பாராத behavior தவிர்க்க, இந்த pattern-ஐ அதிகமாக use செய்ய வேண்டாம். App-wide initialization logic-ஐ App.js போன்ற root component modules-இலோ அல்லது உங்கள் application’s entry point-இலோ வைத்திருங்கள்.

State changes பற்றி parent components-க்கு தெரிவித்தல்

true அல்லது false ஆக இருக்கக்கூடிய internal isOn state கொண்ட Toggle component எழுதுகிறீர்கள் என வைத்துக்கொள்ளுங்கள். அதை toggle செய்ய சில வேறுபட்ட வழிகள் உள்ளன (click செய்வது அல்லது drag செய்வது). Toggle-ன் internal state மாறும் ஒவ்வொரு முறையும் parent component-க்கு தெரிவிக்க விரும்புகிறீர்கள்; எனவே onChange event-ஐ expose செய்து அதை Effect-இலிருந்து call செய்கிறீர்கள்:

function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);

// 🔴 Avoid: The onChange handler runs too late
useEffect(() => {
onChange(isOn);
}, [isOn, onChange])

function handleClick() {
setIsOn(!isOn);
}

function handleDragEnd(e) {
if (isCloserToRightEdge(e)) {
setIsOn(true);
} else {
setIsOn(false);
}
}

// ...
}

முன்னதாக பார்த்தது போல, இது ideal அல்ல. Toggle முதலில் தனது state-ஐ update செய்கிறது, பின்னர் React screen-ஐ update செய்கிறது. அதன் பிறகு React Effect-ஐ run செய்து, parent component-இலிருந்து pass செய்யப்பட்ட onChange function-ஐ call செய்கிறது. இப்போது parent component தனது state-ஐ update செய்து, மற்றொரு render pass-ஐ தொடங்கும். எல்லாவற்றையும் ஒரே pass-இல் செய்வது சிறந்தது.

Effect-ஐ delete செய்து, அதற்கு பதிலாக இரு components-ன் state-ஐயும் அதே event handler-க்குள் update செய்யுங்கள்:

function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);

function updateToggle(nextIsOn) {
// ✅ Good: Perform all updates during the event that caused them
setIsOn(nextIsOn);
onChange(nextIsOn);
}

function handleClick() {
updateToggle(!isOn);
}

function handleDragEnd(e) {
if (isCloserToRightEdge(e)) {
updateToggle(true);
} else {
updateToggle(false);
}
}

// ...
}

இந்த அணுகுமுறையில், Toggle component மற்றும் அதன் parent component இரண்டும் event நேரத்தில் தங்கள் state-ஐ update செய்கின்றன. React வெவ்வேறு components-இலிருந்து வரும் updates-ஐ ஒன்றாக batch செய்கிறது, எனவே ஒரே render pass மட்டுமே இருக்கும்.

State-ஐ முழுமையாக remove செய்து, அதற்கு பதிலாக parent component-இலிருந்து isOn-ஐ பெறவும் முடியும்:

// ✅ Also good: the component is fully controlled by its parent
function Toggle({ isOn, onChange }) {
function handleClick() {
onChange(!isOn);
}

function handleDragEnd(e) {
if (isCloserToRightEdge(e)) {
onChange(true);
} else {
onChange(false);
}
}

// ...
}

“State-ஐ மேலே lifting செய்வது” parent component தனது சொந்த state-ஐ toggle செய்வதன் மூலம் Toggle-ஐ முழுமையாக control செய்ய அனுமதிக்கிறது. இதன் பொருள் parent component அதிக logic கொண்டிருக்க வேண்டியிருக்கும்; ஆனால் மொத்தத்தில் கவலைப்பட வேண்டிய state குறையும். இரண்டு வேறுபட்ட state variables-ஐ synchronized ஆக வைத்திருக்க முயற்சிக்கும் ஒவ்வொரு முறையும், அதற்கு பதிலாக state-ஐ மேலே lift செய்ய முயற்சிக்கவும்!

Parent-க்கு data pass செய்தல்

இந்த Child component சில data-ஐ fetch செய்து, பிறகு அதை Effect-இல் Parent component-க்கு pass செய்கிறது:

function Parent() {
const [data, setData] = useState(null);
// ...
return <Child onFetched={setData} />;
}

function Child({ onFetched }) {
const data = useSomeAPI();
// 🔴 Avoid: Passing data to the parent in an Effect
useEffect(() => {
if (data) {
onFetched(data);
}
}, [onFetched, data]);
// ...
}

React-இல், data parent components-இலிருந்து அவற்றின் children-க்கு flow ஆகிறது. Screen-இல் ஏதாவது தவறாக தெரிந்தால், எந்த component தவறான prop-ஐ pass செய்கிறது அல்லது தவறான state வைத்திருக்கிறது என்பதை கண்டுபிடிக்கும் வரை component chain-இல் மேலே சென்று information எங்கிருந்து வருகிறது என்பதை trace செய்யலாம். Child components Effects-இல் parent components-ன் state-ஐ update செய்தால், data flow-ஐ trace செய்வது மிகவும் கடினமாகும். Child மற்றும் parent இரண்டுக்கும் அதே data தேவைப்படுவதால், parent component அந்த data-ஐ fetch செய்து, அதற்கு பதிலாக child-க்கு கீழே pass செய்யட்டும்:

function Parent() {
const data = useSomeAPI();
// ...
// ✅ Good: Passing data down to the child
return <Child data={data} />;
}

function Child({ data }) {
// ...
}

இது நேரடியானது; மேலும் data flow-ஐ predictable ஆக வைத்திருக்கிறது: data parent-இலிருந்து child-க்கு கீழே flow ஆகிறது.

External store-க்கு subscribe செய்தல்

சில நேரங்களில், உங்கள் components React state-க்கு வெளியே உள்ள சில data-க்கு subscribe செய்ய வேண்டியிருக்கும். இந்த data ஒரு third-party library-இலிருந்தோ அல்லது built-in browser API-இலிருந்தோ வரலாம். React-க்கு தெரியாமலே இந்த data மாறக்கூடியதால், உங்கள் components-ஐ அதற்கு manual ஆக subscribe செய்ய வேண்டும். இது பெரும்பாலும் Effect மூலம் செய்யப்படுகிறது, உதாரணமாக:

function useOnlineStatus() {
// Not ideal: Manual store subscription in an Effect
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function updateState() {
setIsOnline(navigator.onLine);
}

updateState();

window.addEventListener('online', updateState);
window.addEventListener('offline', updateState);
return () => {
window.removeEventListener('online', updateState);
window.removeEventListener('offline', updateState);
};
}, []);
return isOnline;
}

function ChatIndicator() {
const isOnline = useOnlineStatus();
// ...
}

இங்கே, component ஒரு external data store-க்கு subscribe செய்கிறது (இந்த case-இல் browser navigator.onLine API). இந்த API server-இல் இல்லாததால் (அதனால் initial HTML-க்கு use செய்ய முடியாது), ஆரம்பத்தில் state true ஆக set செய்யப்படுகிறது. Browser-இல் அந்த data store-ன் value மாறும் ஒவ்வொரு முறையும், component தனது state-ஐ update செய்கிறது.

இதற்காக Effects use செய்வது பொதுவானதாக இருந்தாலும், external store-க்கு subscribe செய்வதற்காக React-ல் purpose-built Hook உள்ளது; அதையே முன்னுரிமையாக use செய்ய வேண்டும். Effect-ஐ delete செய்து, அதை useSyncExternalStore call ஆக மாற்றுங்கள்:

function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}

function useOnlineStatus() {
// ✅ Good: Subscribing to an external store with a built-in Hook
return useSyncExternalStore(
subscribe, // அதே function-ஐ pass செய்யும் வரை React resubscribe செய்யாது
() => navigator.onLine, // client-இல் value பெறுவது எப்படி
() => true // server-இல் value பெறுவது எப்படி
);
}

function ChatIndicator() {
const isOnline = useOnlineStatus();
// ...
}

Mutable data-ஐ Effect மூலம் React state-க்கு manual ஆக sync செய்வதை விட இந்த அணுகுமுறை குறைவான error-prone. பொதுவாக, மேலுள்ள useOnlineStatus() போன்ற custom Hook எழுதுவீர்கள்; அப்போது individual components-இல் இந்த code-ஐ repeat செய்ய தேவையில்லை. React components-இலிருந்து external stores-க்கு subscribe செய்வது பற்றி மேலும் படிக்கவும்.

Data fetch செய்தல்

பல apps data fetching-ஐ தொடங்க Effects use செய்கின்றன. இவ்வாறு data fetching Effect எழுதுவது மிகவும் பொதுவானது:

function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [page, setPage] = useState(1);

useEffect(() => {
// 🔴 Avoid: Fetching without cleanup logic
fetchResults(query, page).then(json => {
setResults(json);
});
}, [query, page]);

function handleNextPageClick() {
setPage(page + 1);
}
// ...
}

இந்த fetch-ஐ event handler-க்கு move செய்ய வேண்டியதில்லை.

முந்தைய examples-இல் logic-ஐ event handlers-க்குள் வைக்க வேண்டியிருந்தது; அதற்கு இது contradiction போல தோன்றலாம்! ஆனால் fetch செய்வதற்கான முக்கிய காரணம் typing event அல்ல என்பதை கவனியுங்கள். Search inputs பெரும்பாலும் URL-இலிருந்து prepopulate செய்யப்படும்; user input-ஐ தொடாமலேயே Back மற்றும் Forward navigate செய்யலாம்.

page மற்றும் query எங்கிருந்து வருகிறது என்பது முக்கியமல்ல. இந்த component visible ஆக இருக்கும் வரை, current page மற்றும் query-க்கான network data உடன் results-ஐ synchronized ஆக வைத்திருக்க விரும்புகிறீர்கள். இதனால்தான் இது Effect.

ஆனால் மேலுள்ள code-இல் bug உள்ளது. நீங்கள் "hello"-வை வேகமாக type செய்கிறீர்கள் என கற்பனை செய்யுங்கள். அப்போது query "h"-இலிருந்து "he", "hel", "hell", மற்றும் "hello" ஆக மாறும். இது தனித்தனி fetches-ஐ தொடங்கும்; ஆனால் responses எந்த order-இல் வரும் என்பதில் guarantee இல்லை. உதாரணமாக, "hell" response, "hello" response-க்கு பிறகு வரலாம். அது கடைசியாக setResults() call செய்வதால், நீங்கள் தவறான search results காட்டுவீர்கள். இது “race condition” என்று அழைக்கப்படுகிறது: இரண்டு வேறுபட்ட requests ஒன்றுக்கொன்று “race” செய்து, நீங்கள் எதிர்பார்த்ததை விட வேறு order-இல் வந்தன.

Race condition-ஐ fix செய்ய, stale responses-ஐ ignore செய்ய cleanup function சேர்க்க வேண்டும்:

function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [page, setPage] = useState(1);
useEffect(() => {
let ignore = false;
fetchResults(query, page).then(json => {
if (!ignore) {
setResults(json);
}
});
return () => {
ignore = true;
};
}, [query, page]);

function handleNextPageClick() {
setPage(page + 1);
}
// ...
}

உங்கள் Effect data fetch செய்யும்போது, கடைசியாக request செய்யப்பட்ட response தவிர மற்ற எல்லா responses-உம் ignored ஆகும் என்பதை இது உறுதி செய்கிறது.

Data fetching implement செய்வதில் race conditions-ஐ கையாள்வது மட்டுமே சிரமம் அல்ல. Responses-ஐ cache செய்வது (user Back click செய்ததும் previous screen உடனே தோன்ற), server-இல் data fetch செய்வது எப்படி (initial server-rendered HTML spinner பதிலாக fetched content கொண்டிருக்க), மற்றும் network waterfalls-ஐ தவிர்ப்பது எப்படி (ஒவ்வொரு parent-க்காக காத்திருக்காமல் child data fetch செய்ய) என்பதையும் நீங்கள் யோசிக்க வேண்டியிருக்கும்.

இந்த issues React மட்டும் அல்ல, எந்த UI library-க்கும் பொருந்தும். அவற்றை solve செய்வது trivial அல்ல; அதனால் modern frameworks, Effects-இல் data fetch செய்வதை விட efficient ஆன built-in data fetching mechanisms வழங்குகின்றன.

நீங்கள் framework use செய்யவில்லை (உங்களுடையதை build செய்யவும் விரும்பவில்லை) ஆனால் Effects-இலிருந்து data fetching-ஐ இன்னும் ergonomic ஆக்க விரும்பினால், இந்த example போல fetching logic-ஐ custom Hook ஆக extract செய்வதை consider செய்யுங்கள்:

function SearchResults({ query }) {
const [page, setPage] = useState(1);
const params = new URLSearchParams({ query, page });
const results = useData(`/api/search?${params}`);

function handleNextPageClick() {
setPage(page + 1);
}
// ...
}

function useData(url) {
const [data, setData] = useState(null);
useEffect(() => {
let ignore = false;
fetch(url)
.then(response => response.json())
.then(json => {
if (!ignore) {
setData(json);
}
});
return () => {
ignore = true;
};
}, [url]);
return data;
}

Error handling-க்கும் content loading ஆகிறதா என்பதை track செய்யவும் சில logic சேர்க்க நீங்கள் விரும்பலாம். இப்படி ஒரு Hook-ஐ நீங்களே build செய்யலாம் அல்லது React ecosystem-இல் ஏற்கனவே உள்ள பல solutions-இல் ஒன்றை use செய்யலாம். இது மட்டும் framework-ன் built-in data fetching mechanism use செய்வதைப் போல efficient ஆக இருக்காது; இருந்தாலும் data fetching logic-ஐ custom Hook-க்குள் move செய்வது, பின்னர் efficient data fetching strategy-ஐ adopt செய்வதை உதவும்.

பொதுவாக, Effects எழுத வேண்டிய நிலை வந்தால், மேலுள்ள useData போன்ற இன்னும் declarative மற்றும் purpose-built API கொண்ட custom Hook-க்குள் ஒரு functionality-ஐ extract செய்ய முடியுமா என்று கவனித்து இருங்கள். உங்கள் components-இல் raw useEffect calls குறைவாக இருக்கும் அளவுக்கு, application-ஐ maintain செய்வது நேரடியாக இருக்கும்.

Recap

  • Render போது ஏதாவது ஒன்றை calculate செய்ய முடிந்தால், உங்களுக்கு Effect தேவையில்லை.
  • Expensive calculations-ஐ cache செய்ய, useEffect பதிலாக useMemo சேர்க்கவும்.
  • முழு component tree-ன் state-ஐ reset செய்ய, அதற்கு வேறுபட்ட key pass செய்யவும்.
  • Prop change-க்கு response ஆக state-ன் குறிப்பிட்ட பகுதியை reset செய்ய, rendering போது அதை set செய்யவும்.
  • Component காட்டப்பட்டதால் run ஆகும் code Effects-இல் இருக்க வேண்டும்; மற்றவை events-இல் இருக்க வேண்டும்.
  • பல components-ன் state-ஐ update செய்ய வேண்டுமெனில், அதை ஒரே event-இல் செய்வது சிறந்தது.
  • வேறுபட்ட components-இல் உள்ள state variables-ஐ synchronize செய்ய முயற்சிக்கும் ஒவ்வொரு முறையும், state-ஐ மேலே lift செய்வதை consider செய்யுங்கள்.
  • Effects மூலம் data fetch செய்யலாம்; ஆனால் race conditions தவிர்க்க cleanup implement செய்ய வேண்டும்.

Challenge 1 of 4:
Effects இல்லாமல் data-ஐ transform செய்தல்

கீழுள்ள TodoList todos list ஒன்றைக் காட்டுகிறது. “செயலில் உள்ள todos மட்டும் காட்டு” checkbox tick செய்யப்பட்டால், completed todos list-இல் காட்டப்படாது. எந்த todos visible ஆக இருந்தாலும், footer இன்னும் completed ஆகாத todos count-ஐ காட்டும்.

தேவையற்ற state மற்றும் Effects அனைத்தையும் remove செய்து இந்த component-ஐ simplify செய்யுங்கள்.

import { useState, useEffect } from 'react';
import { initialTodos, createTodo } from './todos.js';

export default function TodoList() {
  const [todos, setTodos] = useState(initialTodos);
  const [showActive, setShowActive] = useState(false);
  const [activeTodos, setActiveTodos] = useState([]);
  const [visibleTodos, setVisibleTodos] = useState([]);
  const [footer, setFooter] = useState(null);

  useEffect(() => {
    setActiveTodos(todos.filter(todo => !todo.completed));
  }, [todos]);

  useEffect(() => {
    setVisibleTodos(showActive ? activeTodos : todos);
  }, [showActive, todos, activeTodos]);

  useEffect(() => {
    setFooter(
      <footer>
        {activeTodos.length} todos மீதம்
      </footer>
    );
  }, [activeTodos]);

  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={showActive}
          onChange={e => setShowActive(e.target.checked)}
        />
        செயலில் உள்ள todos மட்டும் காட்டு
      </label>
      <NewTodo onAdd={newTodo => setTodos([...todos, newTodo])} />
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ? <s>{todo.text}</s> : todo.text}
          </li>
        ))}
      </ul>
      {footer}
    </>
  );
}

function NewTodo({ onAdd }) {
  const [text, setText] = useState('');

  function handleAddClick() {
    setText('');
    onAdd(createTodo(text));
  }

  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={handleAddClick}>
        சேர்
      </button>
    </>
  );
}