React Component Lifecycle Sinhala Guide | Class Components vs Hooks (useEffect)

React Component Lifecycle Sinhala Guide | Class Components vs Hooks (useEffect)

ආයුබෝවන් React Developersලා! 🚀

ඔයාලා React එක්ක වැඩ කරනවා නම්, components ජීවත් වෙනවා, වැඩ කරනවා, මැරෙනවා කියන එක ගැන අවබෝධයක් ගන්න එක හරිම වැදගත්. මේක තමයි React Component Lifecycle එක කියලා කියන්නේ. මේ concepts හොඳින් තේරුම් ගත්තම ඔයාලට performance වැඩි, bugs අඩු, පිරිසිදු code ලියන්න පුළුවන් වෙනවා.

ඉතින් අද අපි මේ සිංහල Guide එකෙන් React Component Lifecycle එක ගැන ගැඹුරින් කතා කරමු. Class components වල තිබ්බ lifecycle methods මොනවද, ඒවා කොහොමද වැඩ කළේ, සහ දැන් Hooks (විශේෂයෙන්ම useEffect) එක්ක මේ lifecycle එක manage කරන්නේ කොහොමද කියලා අපි පැහැදිලිව බලමු. ඒ වගේම render phase සහ commit phase කියන්නේ මොනවද කියලත් අපි ඉගෙන ගමු.

ඔයාලා React අලුතින් ඉගෙන ගන්න කෙනෙක් වුණත්, දැනටමත් React එක්ක වැඩ කරන කෙනෙක් වුණත් මේ දැනුම ඔයාලගේ React journey එකට ගොඩක් වටිනවා. එහෙනම්, අපි පටන් ගමු!

Class Component Lifecycle - පදනම 🧱

React මුලින්ම ආව කාලේ components හැදුවේ class-based components විදිහට. මේ class components වලට තමන්ගේ ජීවිත කාලය තුළදී (mount වෙනකොට, update වෙනකොට, unmount වෙනකොට) නිශ්චිත අවස්ථාවලදී ක්‍රියාත්මක වන විශේෂ methods තිබුණා. මේවාට තමයි අපි Lifecycle Methods කියලා කියන්නේ.

Mounting Phase (Component එක පටන් ගන්නකොට)

Component එකක් මුලින්ම DOM එකට එකතු වෙනකොට (mount වෙනකොට) ක්‍රියාත්මක වෙන methods ටික තමයි මේ. හරියට අලුතින් උපන් බබෙක් වගේ!

  • constructor(props):
    • මුලින්ම call වෙන්නේ මේකයි.
    • state එක initialize කරන්නයි, methods bind කරන්නයි පාවිච්චි කරනවා.
    • super(props) call කරන්න අනිවාර්යයි.
  • static getDerivedStateFromProps(props, state):
    • render එකට කලින් call වෙනවා, initial mount එකේදී වගේම updates වලදීත්.
    • props වෙනස් වෙනකොට state එක update කරන්න පාවිච්චි කරනවා.
    • null හෝ object එකක් return කරන්න ඕනේ. Side effects කරන්න මෙතන හොඳ නැහැ.
  • render():
    • Component එකේ UI එක render කරන්නේ මේ method එකෙන්.
    • Pure function එකක් වෙන්න ඕනේ, ඒ කියන්නේ side effects (API calls, state update කිරීම්) කරන්න බැහැ.
    • JSX return කරනවා.
  • componentDidMount():
    • Component එක DOM එකට mount වුණාට පස්සේ (පළමු render එකට පස්සේ) මේක call වෙනවා.
    • API calls කරන්න, subscriptions handle කරන්න, DOM manipulation කරන්න වගේ side effects වලට මේක තමයි හොඳම තැන.
    • මෙතනදී setState call කළොත් තව re-render එකක් වෙනවා, ඒත් browser එකට කලින් render එක පෙන්නන්න අවස්ථාවක් නැහැ.

Updating Phase (Component එක update වෙනකොට)

Component එකේ props හෝ state වෙනස් වුණාම UI එක update වෙන්න ඕනනේ. ඒ වෙලාවට ක්‍රියාත්මක වෙන methods ටික තමයි මේ.

  • static getDerivedStateFromProps(props, state): Mounting එකේදී වගේම, update වෙනකොටත් call වෙනවා.
  • shouldComponentUpdate(nextProps, nextState):
    • Performance optimization වලට මේක පාවිච්චි කරනවා.
    • Component එක re-render කරන්න ඕනේද නැද්ද කියලා තීරණය කරන්නේ මේ method එකෙන් (true හෝ false return කරනවා).
    • සාමාන්‍යයෙන් React.PureComponent හෝ React.memo පාවිච්චි කරනවා නම් මේක manual check කරන්න ඕන වෙන්නේ නැහැ.
  • render(): Props හෝ state වෙනස් වුණාම UI එක update කරන්න ආයෙත් call වෙනවා.
  • getSnapshotBeforeUpdate(prevProps, prevState):
    • render call වුණාට පස්සේ, DOM update වෙන්න කලින් මේක call වෙනවා.
    • DOM එකේ යම් තත්වයක් (scroll position වගේ) save කරගන්න පාවිච්චි කරනවා.
    • Return කරන value එක componentDidUpdate වලට තුන්වෙනි argument එක විදිහට ලැබෙනවා.
  • componentDidUpdate(prevProps, prevState, snapshot):
    • Component එක update වෙලා, DOM එකත් update වුණාට පස්සේ මේක call වෙනවා.
    • props හෝ state වෙනස් වුණාම කරන්න ඕන side effects (API calls, DOM manipulation) වලට මේක පාවිච්චි කරනවා.
    • අනිවාර්යයෙන්ම props හෝ state කලින් තිබ්බ ඒවාට වඩා වෙනස්ද කියලා check කරන්න ඕනේ, නැත්නම් infinite loops ඇති වෙන්න පුළුවන්.

Unmounting Phase (Component එක ඉවත් වෙනකොට)

Component එක DOM එකෙන් ඉවත් වෙනකොට (destroy වෙනකොට) ක්‍රියාත්මක වෙන method එක තමයි මේ.

  • componentWillUnmount():
    • Component එක DOM එකෙන් ඉවත් වෙන්න කලින් මේක call වෙනවා.
    • componentDidMount එකේදී පටන් ගත්ත timers, network requests, subscriptions වගේ දේවල් cleanup කරන්න මේක පාවිච්චි කරනවා. Memory leaks නවත්තන්න මේක අනිවාර්යයි!

Class Component Lifecycle Example

මේ code එකෙන් බලමු Class Component lifecycle methods ටික කොහොමද වැඩ කරන්නේ කියලා:

import React, { Component } from 'react';

class LifecycleDemo extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    console.log('1. Constructor called');
  }

  static getDerivedStateFromProps(props, state) {
    console.log('2. getDerivedStateFromProps called');
    return null; // Or return { new_state: ... }
  }

  componentDidMount() {
    console.log('4. componentDidMount called - Component mounted to DOM');
    // API calls, subscriptions can go here
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('5. shouldComponentUpdate called');
    // Only re-render if count actually changes
    return nextState.count !== this.state.count;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('6. getSnapshotBeforeUpdate called');
    // Capture some DOM info before update
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('7. componentDidUpdate called - Component re-rendered');
    if (prevState.count !== this.state.count) {
      console.log('Count was updated to:', this.state.count);
      // Perform side effects based on count change
    }
  }

  componentWillUnmount() {
    console.log('8. componentWillUnmount called - Component unmounting');
    // Clean up subscriptions, timers, etc.
  }

  handleClick = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };

  render() {
    console.log('3. Render called');
    return (
      <div>
        <h3>Class Component Lifecycle Demo</h3>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment Count</button>
        {this.state.count > 2 && <p>Count is high!</p>}
      </div>
    );
  }
}

export default LifecycleDemo;

Render සහ Commit Phases - React වැඩ කරන විදිහ ⚙️

React component එකක් update වෙනකොට ප්‍රධාන phases දෙකක් තියෙනවා. මේවා තේරුම් ගන්න එක useEffect හරියට පාවිච්චි කරන්න මාර වැදගත්.

1. Render Phase

  • මොකක්ද වෙන්නේ? React UI එක කොහොමද පේන්න ඕනේ කියලා ගණනය කරනවා. මේ phase එකේදී React එකේ Virtual DOM එක update වෙනවා.
  • මොනවද ඇතුළත් වෙන්නේ?
    • Class components වල render() method එක.
    • Functional components වල function component body එකේ code එක.
    • static getDerivedStateFromProps.
    • shouldComponentUpdate.
  • වැදගත් දේ: මේක pure function එකක් වගේ ක්‍රියාත්මක වෙන්න ඕනේ. ඒ කියන්නේ, side effects (DOM manipulation, API calls, state updates) කරන්න බැහැ! මේ phase එකේදී කිහිප වතාවක් call වෙන්න පුළුවන් (උදා: React Strict Mode එකේදී).

2. Commit Phase

  • මොකක්ද වෙන්නේ? React Virtual DOM එකේ සිදු වූ වෙනස්කම් ඇත්ත DOM එකට apply කරනවා. ඒ කියන්නේ UI එක display කරනවා.
  • මොනවද ඇතුළත් වෙන්නේ?
    • Class components වල componentDidMount, componentDidUpdate, componentWillUnmount.
    • Functional components වල useEffect callbacks (සහ ඒවායේ cleanup functions).
    • getSnapshotBeforeUpdate.
  • වැදගත් දේ: Side effects කරන්න මේක තමයි ආරක්ෂිතම තැන. DOM එක update වෙලා ඉවරයි, ඒ නිසා ඔයාලට DOM elements වලට access කරන්න පුළුවන්.

සරලව කිව්වොත්: Render Phase එක කියන්නේ 'ගණනය කරන එක', Commit Phase එක කියන්නේ 'ක්‍රියාත්මක කරන එක'. මේ වෙනස තේරුම් ගන්න එකෙන් අනවශ්‍ය re-renders වළක්වා ගන්නත්, bugs ඇතිවීම අඩු කරගන්නත් පුළුවන්.

Hooks සමග Lifecycle - useEffect වල බලය ✨

React 16.8 එක්ක හඳුන්වා දුන්නු React Hooks, functional components වලට state සහ lifecycle features එකතු කළා. Class components වල තිබ්බ lifecycle methods වලට වඩා useEffect කියන Hook එකෙන් lifecycle events handle කරන එක හරිම පහසු කළා. මේක තමයි component lifecycle concerns combine කරන single API එක.

useEffect කියන්නේ මොකක්ද?

useEffect කියන්නේ functional component එකක side effects handle කරන්න පාවිච්චි කරන Hook එකක්. Side effects කියන්නේ මොනවද? Data fetching, subscriptions, manual DOM manipulations, timers වගේ දේවල්.

useEffect method එකට arguments දෙකක් තියෙනවා:

  1. Callback function එකක්: මේක තමයි ඔයාලාගේ side effect එක අඩංගු code block එක.
  2. Dependency Array එකක් (Optional): මේක තමයි useEffect call වෙන්න ඕනේ කවදද කියලා තීරණය කරන්නේ.

useEffect සහ Dependency Array එක 🎯

useEffect එක Class component lifecycle methods වලට map වෙන්නේ මේ Dependency Array එක මතයි:

    • Dependency Array එක හිස් නම් ([]), මේ useEffect එක call වෙන්නේ component එක මුලින්ම mount වුණාම එක පාරක් විතරයි.
    • හරියට Class component එකක componentDidMount වගේ.
    • ඒ වගේම මේ callback function එකෙන් function එකක් return කළොත්, ඒක component එක unmount වෙනකොට call වෙනවා. මේක cleanup function එකක්. හරියට componentWillUnmount වගේ.
    • Dependency Array එක මුකුත් නැත්නම්, මේ useEffect එක call වෙන්නේ component එක mount වුණාම සහ සෑම re-render එකකට පස්සේම.
    • මේක Class component එකක componentDidMount සහ componentDidUpdate දෙකම එකට වැඩ කරනවා වගේ.
    • මේක පාවිච්චි කරන එකෙන් පරිස්සම් වෙන්න ඕනේ, මොකද අනවශ්‍ය re-renders සහ performance issues ඇති වෙන්න පුළුවන්.
    • Dependency Array එකේ values තියෙනවා නම්, මේ useEffect එක call වෙන්නේ component එක mount වුණාම සහ ඒ dependencies වල values වෙනස් වුණාම විතරයි.
    • මේක Class component එකක componentDidMount සහ componentDidUpdate වලදී specific props හෝ state values වෙනස්ද කියලා check කරලා side effects කරනවා වගේ.
    • මේක තමයි useEffect පාවිච්චි කරන වඩාත්ම සුලභ සහ effective ක්‍රමය.

useEffect(() => { ... }, [dep1, dep2]) - Specific Dependencies Changed (componentDidMount + componentDidUpdate conditionally)

import React, { useEffect, useState } from 'react';

function DependentEffectDemo({ userId }) {
  const [userDetails, setUserDetails] = useState(null);

  useEffect(() => {
    if (!userId) return; // Don't fetch if no user ID
    console.log(`Fetching user details for userId: ${userId}`);

    const fetchUserDetails = async () => {
      setUserDetails(null); // Clear previous user details
      const response = await new Promise(resolve => setTimeout(() => resolve({
        id: userId, name: `User ${userId}`, email: `user${userId}@example.com`
      }), 1500));
      setUserDetails(response);
      console.log(`User ${userId} details fetched.`);
    };

    fetchUserDetails();

    return () => {
      console.log(`Cleanup for userId: ${userId} - component or userId changed.`);
      // Any cleanup specific to this userId
    };
  }, [userId]); // Runs when userId changes (or on initial mount)

  return (
    <div>
      <h3>useEffect with Dependencies</h3>
      <p>Current User ID: {userId || 'None'}</p>
      {userDetails ? (
        <div>
          <p>Name: {userDetails.name}</p>
          <p>Email: {userDetails.email}</p>
        </div>
      ) : (
        <p>{userId ? 'Loading user details...' : 'Please provide a User ID.'}</p>
      )}
    </div>
  );
}

export default DependentEffectDemo;

useEffect(() => { ... }) - Every Render (componentDidMount + componentDidUpdate)

useEffect(() => { ... }, []) - Mount & Unmount (componentDidMount & componentWillUnmount)

import React, { useEffect, useState } from 'react';

function MountUnmountDemo() {
  const [data, setData] = useState(null);

  useEffect(() => {
    console.log('Fetching data...');
    // Simulate API call
    const fetchData = async () => {
      const response = await new Promise(resolve => setTimeout(() => resolve('Some Data Fetched!'), 2000));
      setData(response);
      console.log('Data fetched!');
    };

    fetchData();

    // Cleanup function (like componentWillUnmount)
    return () => {
      console.log('Cleanup: Component will unmount.');
      // Cancel subscriptions, clear timers, etc.
    };
  }, []); // Empty dependency array: runs only on mount and cleanup on unmount

  return (
    <div>
      <h3>useEffect - Mount and Unmount</h3>
      {data ? <p>Data: {data}</p> : <p>Loading data...</p>}
    </div>
  );
}

export default MountUnmountDemo;

Class Components vs. Hooks Lifecycle - වෙනස්කම් සහ Best Practices 🔄

දැන් අපි Class components වල lifecycle methods ගැනයි, useEffect ගැනයි දැනුවත් නිසා, මේ දෙක අතර තියෙන ප්‍රධාන වෙනස්කම් සහ Hooks පාවිච්චි කරනකොට මතක තියාගන්න ඕන Best Practices ටිකක් බලමු.

ප්‍රධාන වෙනස්කම්

  1. Execution Order:
    • Class Components: componentDidMount එක run වෙන්නේ initial render එකට පස්සේ එක පාරයි. componentDidUpdate run වෙන්නේ subsequent renders වලට පස්සේ.
    • Hooks (useEffect): useEffect එකේ callback function එක සෑම render එකකටම පස්සේ run වෙනවා (dependencies වෙනස් වෙලා නම්). ඒ කියන්නේ, React DOM එක update කරලා ඉවර වුණාට පස්සේ. මේක Class components වලට වඩා වෙනස්. මේ නිසා useEffect එක ඇතුළේ state එක update කරනකොට පරිස්සම් වෙන්න ඕනේ, නැත්නම් infinite loops ඇති වෙන්න පුළුවන්.
  2. Separation of Concerns (කාර්යයන් වෙන් කිරීම):
    • Class Components: එක side effect එකක් (උදා: data fetching) විවිධ lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) වලට කෑලි කෑලි වලට බෙදිලා යන්න පුළුවන්.
    • Hooks (useEffect): useEffect එකෙන් එකම තැනකදී එකම side effect එකට අදාළ හැම logic එකක්ම (setup සහ cleanup) තියාගන්න පුළුවන්. මේකෙන් code එක කියවන්න සහ maintain කරන්න පහසු වෙනවා.
  3. Readability & Reusability:
    • Hooks, functional components වලට තව පවර් එකතු කරලා, code එක වඩාත් clean, concise සහ reusable කරන්න උදව් කරනවා.
    • Custom Hooks හදලා complex logic share කරන්නත් පුළුවන්.

Common Pitfalls (සුලභ ගැටළු)

  • Not Cleaning Up:componentWillUnmount එකේදී වගේ, useEffect එකෙන් return කරන cleanup function එක පාවිච්චි නොකළොත් memory leaks, subscriptions දෙපාරක් call වීම් වගේ issues ඇති වෙන්න පුළුවන්.

Infinite Loops with useEffect:Dependency Array එකට object එකක් හෝ array එකක් inline විදිහට දෙනකොට, සෑම render එකකදීම අලුත් object/array එකක් හැදෙනවා. මේක නිසා useEffect එක සෑම render එකකටම පස්සේ run වෙන්න පුළුවන්, මොකද React හිතන්නේ dependency එක වෙනස් වුණා කියලා. මේක වළක්වන්න useCallback, useMemo පාවිච්චි කරන්න පුළුවන්.

function InfiniteLoopExample() {
  const [value, setValue] = useState(0);

  // BAD EXAMPLE: 'config' object is created on every render, causing infinite loop
  useEffect(() => {
    console.log('Effect ran due to config change');
    // setValue(prev => prev + 1); // This would cause infinite loop if not careful
  }, [{ someProp: value }]); // <-- Problem: new object created on every render

  return (
    <div>
      <h3>Infinite Loop Pitfall</h3>
      <p>Value: {value}</p>
      <button onClick={() => setValue(value + 1)}>Change Value</button>
    </div>
  );
}

Missing Dependencies in useEffect (Stale Closures):useEffect එකේ Dependency Array එක නිවැරදිව දෙන්නේ නැත්නම්, callback එක ඇතුළේ පාවිච්චි කරන variables (state, props, functions) වලට පැරණි values තියාගෙන වැඩ කරන්න පුළුවන්. මේකට stale closures කියලා කියනවා. මේක නිසා unexpected behavior ඇති වෙන්න පුළුවන්.

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

  useEffect(() => {
    // count value will always be 0 here if [] is used as dependency
    const id = setInterval(() => {
      console.log('Current Count (stale):', count); // This might always log 0 if count is missing from dependency array
      // To fix: setCount(prevCount => prevCount + 1); OR add count to dependency array
    }, 1000);
    return () => clearInterval(id);
  }, []); // Problem: count is not in dependency array

  return (
    <div>
      <h3>Stale Closure Example</h3>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Best Practices for useEffect

  • Always specify dependencies: useEffect එකේ callback එක ඇතුළේ පාවිච්චි කරන හැම variable (state, props, functions) එකක්ම Dependency Array එකට දෙන්න. ESLint plugin (eslint-plugin-react-hooks) එක මේකට ගොඩක් උදව් කරනවා.
  • Return cleanup functions: Subscriptions, timers, event listeners වගේ දේවල් setup කරනකොට අනිවාර්යයෙන්ම cleanup කරන්න return function එකක් දෙන්න.
  • Separate concerns: එක useEffect එකක් ඇතුළේ ගොඩක් දේවල් නොකර, විවිධ side effects වලට වෙන වෙනම useEffect calls පාවිච්චි කරන්න. මේකෙන් code එක කියවන්න සහ maintain කරන්න පහසු වෙනවා.
  • Use useCallback and useMemo for complex dependencies: Object හෝ array එකක් Dependency Array එකට දෙනකොට, සෑම render එකකදීම අලුතින් හැදෙන එක වළක්වන්න useCallback (functions වලට) හෝ useMemo (values වලට) පාවිච්චි කරන්න.

අවසන් වශයෙන් (Conclusion) 🎉

ඉතින් React Component Lifecycle එක කියන්නේ React Developersලා විදිහට අපි හැමෝම දැනගෙන ඉන්න ඕන මූලිකම concept එකක්. Class components වල lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) වලින් ලැබුණු පදනම මත තමයි අද අපි useEffect වගේ Hooks පාවිච්චි කරන්නේ.

useEffect කියන්නේ functional components වල side effects manage කරන්න තියෙන හරිම බලගතු සහ flexible tool එකක්. ඒත් ඒක හරියට පාවිච්චි කරන්න Dependency Array එකේ වැදගත්කම, cleanup functions වල අවශ්‍යතාවය සහ Render/Commit phases අතර වෙනස හොඳින් තේරුම් ගන්න එක අනිවාර්යයි.

දැන් ඔයාලාට React components ජීවත් වන, වැඩ කරන සහ මැරෙන විදිහ ගැන හොඳ අවබෝධයක් තියෙනවා. මේ දැනුමෙන් ඔයාලගේ applications වඩාත් stable, efficient සහ bug-free කරන්න පුළුවන්.

දැන් ඔයාලාගේ ඊළඟ React project එකේ මේ concepts යොදාගෙන බලන්න. Code කරලා, experiment කරලා, වැරදිලා ඉගෙන ගන්න!

මේ tutorial එක ගැන ඔයාලගේ අදහස්, ප්‍රශ්න හෝ අත්දැකීම් පහළින් comment එකක් දාලා බෙදාගන්න. අපි ඊළඟ article එකෙන් හමුවෙමු! Happy Coding! 👋