React useContext Hook | Context API Sinhala Tutorial | Prop Drilling Solution

React useContext Hook | Context API Sinhala Tutorial | Prop Drilling Solution

හලෝ යාළුවනේ! කොහොමද ඉතින්? අද අපි කතා කරන්න යන්නේ React application එකක් develop කරන ඕනෑම කෙනෙක්ට ගොඩක් වැදගත් වෙන, ඒ වගේම ගොඩක් ප්‍රයෝජනවත් වෙන දෙයක් ගැන – ඒ තමයි React Context API එකයි, ඒකත් එක්ක වැඩ කරන useContext Hook එකයි.

React වල state management කියන්නේ ටිකක් සංකීර්ණ වෙන්න පුළුවන් දෙයක්. හැබැයි Context API එක හරියට තේරුම් ගත්තොත්, ඔයාගේ code එක ගොඩක් ලස්සනට, පහසුවෙන් maintain කරන්න පුළුවන් විදිහට ලියන්න පුළුවන් වෙනවා.

අද අපි මේ Tutorial එකෙන් මොනවද ඉගෙන ගන්නේ?

  • Prop Drilling කියන්නේ මොකක්ද? ඒක ඇයි ප්‍රශ්නයක් වෙන්නේ?
  • Prop Drilling ගැටලුවට Context API එකෙන් විසඳුම් දෙන්නේ කොහොමද?
  • createContext, Provider, සහ useContext කියන ප්‍රධාන සංකල්ප මොනවද කියලා පැහැදිලිව තේරුම් ගමු.
  • Dark/Light Theme එකක් Context API එක පාවිච්චි කරලා develop කරන්නේ කොහොමද කියලා ප්‍රායෝගික උදාහරණයක් හරහා බලමු.
  • Context API භාවිතා කිරීමේදී සැලකිලිමත් විය යුතු හොඳම භාවිතයන් (Best Practices) සහ පොදු ගැටළු (Common Issues) ගැනත් කතා කරමු.

මේ tutorial එක අවසානෙදි ඔයාට පුළුවන් වෙයි confidently ඔයාගේ React project වල global state manage කරන්න Context API එක භාවිතා කරන්න. එහෙනම්, අපි පටන් ගමු!

1. Prop Drilling කියන්නේ මොකක්ද? (Prop Drilling Explained)

ඔයා React එක්ක වැඩ කරනකොට මේ Prop Drilling කියන වචනය අහලා ඇති, නැත්නම් ඒ ගැටලුවට මුහුණ දීලා ඇති. සරලවම කිව්වොත්, Prop Drilling කියන්නේ අපිට අවශ්‍ය කරන state එකක් (දත්තයක්) එක component එකක ඉඳන් තවත් ගොඩක් ඈතින් තියෙන component එකකට යවන්න, අතරමැද තියෙන අනවශ්‍ය components ගොඩක් හරහා props විදිහට යවන්න වෙන එකයි.

මෙහෙම හිතමුකෝ:

ඔයාට App කියන root component එකේ userName කියන state එක තියෙනවා. මේ userName එක ඔයාට ඕනේ App > Dashboard > Sidebar > UserProfileCard කියන component path එකේ තියෙන UserProfileCard එකට. එතකොට ඔයාට මේ userName එක props විදිහට App ඉඳන් Dashboard එකටත්, Dashboard එකේ ඉඳන් Sidebar එකටත්, Sidebar එකේ ඉඳන් UserProfileCard එකටත් යවන්න වෙනවා. Dashboard එකටවත් Sidebar එකටවත් ඇත්තටම userName එක අවශ්‍ය වෙන්නේ නැහැ, ඒත් ඒක UserProfileCard එකට යවන්න එයාලට අතරමැදි පාලමක් විදිහට ක්‍රියා කරන්න වෙනවා.

මෙන්න මේක තමයි prop drilling.

Prop Drilling එක ගැටලුවක් වෙන්නේ ඇයි?

  • Code එක කියවන්න අමාරු වෙනවා (Readability): Component එකක් දැක්කම ඒකට ඇත්තටම අවශ්‍ය කරන props මොනවද කියලා හොයාගන්න අමාරු වෙනවා. අනවශ්‍ය props ගොඩක් තියෙන්න පුළුවන්.
  • නඩත්තු කරන්න අමාරු වෙනවා (Maintainability): ඔයාට state එකේ නම වෙනස් කරන්න වුණොත්, නැත්නම් data type එක වෙනස් කරන්න වුණොත්, ඒක යන හැම component එකකම වෙනස්කම් කරන්න වෙනවා. මේක ලොකු project එකකදී ලොකු හිසරදයක් වෙන්න පුළුවන්.
  • Code එක cluttered වෙනවා: අතරමැද components අනවශ්‍ය props වලින් පිරිලා, වැඩේ අවුල් වෙනවා.

හරි, දැන් ඔයාට prop drilling කියන්නේ මොකක්ද කියලා හොඳට තේරෙන්න ඇති. දැන් අපි බලමු මේ ගැටලුවට React Context API එකෙන් දෙන විසඳුම මොකක්ද කියලා.

2. Context API ගැටලුවට විසඳුම (Context API: The Solution)

React Context API එක design කරලා තියෙන්නේ මේ වගේ global state (theme, user authentication වගේ application එක පුරාම අවශ්‍ය වෙන දත්ත) කිසිම prop drilling එකකින් තොරව, අවශ්‍ය කරන ඕනෑම component එකකට කෙලින්ම ලබාගන්න පුළුවන් විදිහට.

Context API එකේ ප්‍රධාන කොටස් තුනක් තියෙනවා:

  1. createContext: Context එකක් නිර්මාණය කරන්න.
  2. Provider: Context එකේ තියෙන data ටික, එයාට යටින් තියෙන හැම component එකකටම (child components) ලබාදෙන්න.
  3. useContext: ඕනෑම child component එකකට, Provider එකෙන් දෙන data ටික ලබාගන්න (consume කරන්න).

මේවා වැඩ කරන්නේ කොහොමද?

ඔයා මුලින්ම createContext එකෙන් Context object එකක් හදනවා. මේ object එකට Provider සහ Consumer (අපි useContext hook එක පාවිච්චි කරන නිසා Consumer ගැන වැඩිය හිතන්න ඕනේ නැහැ) කියන components දෙකක් තියෙනවා.

ඊට පස්සේ, ඔයාගේ application එකේ, ඔයාට global state එක බෙදාගන්න ඕනෑ කරන කොටසට උඩින් Provider component එක දානවා. මේ Provider එකට value කියන prop එකක් දෙනවා. මේ value එක තමයි ඔයාගේ global state එක. Provider එකට යටින් තියෙන හැම component එකකටම මේ value එකට access කරන්න පුළුවන් වෙනවා.

අවසානෙට, ඔයාට global state එක අවශ්‍ය කරන ඕනෑම component එකකදී useContext(YourContext) කියන hook එක පාවිච්චි කරලා, Provider එකෙන් දෙන value එක කෙලින්ම ලබාගන්න පුළුවන්. අතරමැද තියෙන components වලට ඒ ගැන දැනගන්නවත්, props විදිහට යවන්නවත් අවශ්‍ය වෙන්නේ නැහැ.

හරි, දැන් theoretical පැත්තෙන් ඇති. අපි දැන් බලමු මේක ප්‍රායෝගිකව කරන්නේ කොහොමද කියලා.

3. ප්‍රායෝගික උදාහරණය: Dark/Light Theme Switcher (Practical Example: Dark/Light Theme Switcher)

මේ උදාහරණයෙන් අපි බලමු Dark Mode සහ Light Mode අතර switch වෙන්න පුළුවන් theme switcher එකක් Context API එකෙන් implement කරන්නේ කොහොමද කියලා. මේක ගොඩක් React applications වල තියෙන common feature එකක්.

මුලින්ම අපි project එකක් හදාගමු:

npx create-react-app theme-switcher-app
cd theme-switcher-app
npm start

පියවර 1: Context එක හදාගමු (Create the Context)

src folder එක ඇතුළේ ThemeContext.js කියලා අලුත් file එකක් හදමු.

// src/ThemeContext.js
import React, { createContext, useState, useContext } from 'react';

// මුලින්ම Context object එක හදනවා
// defaultValue එක විදිහට 'light' සහ toggleTheme function එකක් දෙනවා.
// මේවා Provider එකෙන් override වෙනවා.
export const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {},
});

// ThemeProvider component එක හදාගමු
// මේකෙන් තමයි අපේ application එකේ අනිත් components වලට theme state එක provide කරන්නේ
export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light'); // 'light' හෝ 'dark'

  const toggleTheme = () => {
    setTheme((currentTheme) => (currentTheme === 'light' ? 'dark' : 'light'));
  };

  const contextValue = {
    theme,
    toggleTheme,
  };

  return (
    <ThemeContext.Provider value={contextValue}>
      {children}
    </ThemeContext.Provider>
  );
};

// පහසුවෙන් useContext hook එක භාවිතා කරන්න custom hook එකක් හදමු
export const useTheme = () => useContext(ThemeContext);

පැහැදිලි කිරීම:

  • createContext: අපිට ThemeContext කියලා object එකක් දෙනවා. මේකේ Provider එකයි Consumer එකයි තියෙනවා. අපි initial value එකක් විදිහට object එකක් දුන්නා, ඒක අපේ Provider එකෙන් override වෙනවා.
  • ThemeProvider: මේක සාමාන්‍ය React component එකක්. මේක ඇතුළේ අපි useState hook එක පාවිච්චි කරලා theme කියන state එක manage කරනවා. toggleTheme කියන function එකත් මේක ඇතුළේ තියෙනවා.
  • ThemeContext.Provider: මේක තමයි මැජික් එක කරන්නේ. අපි value prop එකට theme state එකයි toggleTheme function එකයි දෙනවා. මේ Provider එකට යටින් render වෙන ඕනෑම component එකකට මේ value එකට access කරන්න පුළුවන්.
  • useTheme custom hook එක: useContext(ThemeContext) හැම තැනකම ලියන එක වෙනුවට, මේ වගේ custom hook එකක් හදාගත්තම code එක ගොඩක් clean වෙනවා.

පියවර 2: App එකට Provider එක එකතු කරමු (Integrate Provider in App.js)

දැන් අපි src/App.js file එක වෙනස් කරමු. අපේ application එකේ root component එක වන App එක ThemeProvider එකෙන් wrap කරමු. එතකොට අපේ application එකේ ඕනෑම තැනකට theme state එකට access කරන්න පුළුවන්.

// src/App.js
import './App.css';
import { ThemeProvider, useTheme } from './ThemeContext';

// ThemeSwitcherButton component එක ThemeContext එක consume කරනවා.
function ThemeSwitcherButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme} className={`theme-button ${theme}`} >
      {theme === 'light' ? 'Switch to Dark' : 'Switch to Light'}
    </button>
  );
}

// ContentComponent එකත් ThemeContext එක consume කරනවා.
function ContentComponent() {
  const { theme } = useTheme();

  return (
    <div className={`content-box ${theme}`} >
      <h3>Current Theme: {theme.toUpperCase()}</h3>
      <p>This content will change its styling based on the current theme.</p>
    </div>
  );
}

// App component එක ThemeProvider එකෙන් wrap කරලා තියෙනවා.
function App() {
  // App component එකට theme එක අවශ්‍ය නැතත්,
  // එයාගේ children (ThemeSwitcherButton, ContentComponent) වලට theme එක ලබා දෙන්න පුළුවන්.
  // මේක තමයි prop drilling වලට විසඳුම.
  return (
    <ThemeProvider>
      <div className="App">
        <h1>React Context API Theme Switcher</h1>
        <ThemeSwitcherButton />
        <ContentComponent />
      </div>
    </ThemeProvider>
  );
}

export default App;

පැහැදිලි කිරීම:

  • App component එක ThemeProvider එකෙන් wrap කරලා තියෙන නිසා, App ඇතුළේ තියෙන ඕනෑම component එකකට (ThemeSwitcherButton සහ ContentComponent වගේ) theme state එකටයි toggleTheme function එකටයි access කරන්න පුළුවන්.
  • ThemeSwitcherButton සහ ContentComponent කියන දෙකම useTheme() custom hook එක පාවිච්චි කරලා, කෙලින්ම Context එකෙන් අවශ්‍ය දත්ත (theme සහ toggleTheme) ලබාගන්නවා. මෙතැනදී props passing එකක් සිද්ධ වෙන්නේ නැහැ.

පියවර 3: Styling එක එකතු කරමු (Add Styling - App.css)

අපිට Dark/Light modes වලට වෙන වෙනම styling දෙන්න src/App.css file එකට මේ CSS ටික එකතු කරමු.

/* src/App.css */
.App {
  font-family: sans-serif;
  text-align: center;
  padding: 20px;
  min-height: 100vh;
  transition: background-color 0.3s ease, color 0.3s ease;
}

.App.light {
  background-color: #f0f2f5;
  color: #333;
}

.App.dark {
  background-color: #333;
  color: #f0f2f5;
}

.content-box {
  margin: 40px auto;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  max-width: 600px;
  transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease;
}

.content-box.light {
  background-color: #ffffff;
  color: #333;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.content-box.dark {
  background-color: #222;
  color: #f0f2f5;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

.theme-button {
  padding: 10px 20px;
  font-size: 16px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.3s ease, color 0.3s ease;
}

.theme-button.light {
  background-color: #007bff;
  color: white;
}

.theme-button.light:hover {
  background-color: #0056b3;
}

.theme-button.dark {
  background-color: #6c757d;
  color: white;
}

.theme-button.dark:hover {
  background-color: #5a6268;
}

දැන් ඔයාගේ application එක run කරලා බලන්න. Theme switcher button එක click කරනකොට application එකේ theme එක Dark සහ Light අතර මාරු වෙනවා දකින්න පුළුවන්.

මේ උදාහරණයෙන් ඔයාට පැහැදිලි වෙන්න ඇති Context API එක කොච්චර ප්‍රයෝජනවත්ද කියලා. අපිට theme එකට අදාළ state එක අවශ්‍ය කරන component එකට කෙලින්ම ලබාගන්න පුළුවන් වුණා. අතරමැද components වලට (මේ උදාහරණයේදී App component එකට) theme state එක prop එකක් විදිහට pass කරන්න අවශ්‍ය වුණේ නැහැ.

4. User Authentication State බෙදාගැනීම (Sharing User Authentication State)

Theme එකක් share කළා වගේම, Context API එක user authentication status වගේ දේවල් share කරන්නත් ගොඩක් හොඳ විසඳුමක්. උදාහරණයක් විදිහට, user කෙනෙක් login වෙලාද නැද්ද, එයාගේ userName එක මොකක්ද, role එක මොකක්ද වගේ දේවල් application එකේ ගොඩක් components වලට අවශ්‍ය වෙන්න පුළුවන්.

අපි හිතමු ඔයාට AuthContext එකක් තියෙනවා කියලා:

// src/AuthContext.js (Conceptual Example)
import React, { createContext, useState, useContext, useEffect } from 'react';

export const AuthContext = createContext({
  isAuthenticated: false,
  user: null,
  login: () => {},
  logout: () => {},
});

export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [user, setUser] = useState(null);

  // සාමාන්‍යයෙන් user authentication status එක localStorage එකෙන් load කරයි
  useEffect(() => {
    const storedUser = JSON.parse(localStorage.getItem('user'));
    if (storedUser) {
      setIsAuthenticated(true);
      setUser(storedUser);
    }
  }, []);

  const login = (userData) => {
    setIsAuthenticated(true);
    setUser(userData);
    localStorage.setItem('user', JSON.stringify(userData));
    // API call for actual login
  };

  const logout = () => {
    setIsAuthenticated(false);
    setUser(null);
    localStorage.removeItem('user');
    // API call for actual logout
  };

  const contextValue = {
    isAuthenticated,
    user,
    login,
    logout,
  };

  return (
    <AuthContext.Provider value={contextValue}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

මේ වගේ AuthProvider එකක් ඔයාගේ App.js එකේ root component එකට උඩින් දාලා, ඕනෑම component එකකදී useAuth() hook එකෙන් isAuthenticated status එකයි, user object එකයි, login, logout functions ටිකයි පහසුවෙන් ලබාගන්න පුළුවන්. ඒකෙන් navigation bar එකක "Login" / "Logout" buttons මාරු කරන එක, user-specific content පෙන්වන එක වගේ දේවල් කරන්න පුළුවන්.

5. හොඳම භාවිතයන් සහ පොදු ගැටළු (Best Practices & Common Issues)

Context API එක ගොඩක් ප්‍රයෝජනවත් වුණත්, ඒක අනිසි ලෙස භාවිතා කිරීමෙන් project එකේ performance වලට බලපෑම් ඇති වෙන්න පුළුවන්. ඒ නිසා, මේ ගැන අවධානය යොමු කිරීම වැදගත්.

Context අනිසි ලෙස භාවිතා කිරීම (Over-using Context)

හැම state එකක්ම Context එක හරහා share කරන්න උත්සාහ කරන්න එපා. Context API එක නිර්මාණය කරලා තියෙන්නේ genuinely global state සඳහා. උදාහරණයක් විදිහට:

  • Theme එක (Dark/Light mode)
  • User Authentication status
  • Preferred Language / Locale
  • Shopping Cart (කුඩා application එකකදී)

මේ වගේ දේවල් තමයි Context එකට වඩාත් සුදුසු. Component එකකට පමණක් අවශ්‍ය වන state එකක් නම් (උදාහරණයක් විදිහට form input field එකක value එකක්, dropdown එකක open/close status එකක්), ඒකට useState හෝ useReducer වගේ component-specific state management solutions භාවිතා කරන එක තමයි වඩා හොඳ.

Context re-renders (Context එක update වෙනකොට components re-render වීම)

මේක Context API එකේ වැදගත් කාරණයක්, ඒ වගේම පොදු වැරදීමක්. Context Provider එකක value prop එක වෙනස් වෙන හැම වෙලාවකම, ඒ Context එක consume කරන (useContext භාවිතා කරන) Provider එකට යටින් තියෙන හැම component එකක්ම re-render වෙනවා.

අපි උඩින් හදපු ThemeContext එකේ value එකේ theme එකයි toggleTheme එකයි දෙකම තියෙනවා. theme එක වෙනස් වෙනකොට, value object එකම අලුතෙන් හැදෙනවා. මේ නිසා, useTheme() hook එක පාවිච්චි කරන හැම component එකක්ම re-render වෙනවා. ලොකු application එකකදී මේක performance issue එකක් වෙන්න පුළුවන්, මොකද අනවශ්‍ය components ගොඩක් re-render වෙන නිසා.

ගැටලුවට විසඳුම් / Best Practices:

  1. Context එක කුඩා කොටස් වලට කඩන්න (Split Contexts): ඔයාට ගොඩක් data share කරන්න තියෙනවා නම්, එක Context එකක් වෙනුවට Context කිහිපයක් භාවිතා කරන්න. උදාහරණයක් විදිහට, ThemeContext එකක්, AuthContext එකක්, CartContext එකක් වගේ.එතකොට AuthContext එකේ data වෙනස් වුණාම ThemeContext එක consume කරන components re-render වෙන්නේ නැහැ. මේකෙන් performance එක improve කරගන්න පුළුවන්.
  2. Redux, Zustand වැනි Global State Management Libraries භාවිතා කිරීම: ගොඩක් සංකීර්ණ සහ විශාල applications වලදී Context API එකේ සීමාවන් නිසා Redux, Zustand, Jotai වගේ dedicated global state management libraries භාවිතා කරන එක වඩා සුදුසු වෙන්න පුළුවන්. ඒ library වල Context API එකට වඩා optimized re-rendering mechanisms තියෙනවා.

Memoization භාවිතා කරන්න (Use Memoization): React.memo, useCallback, useMemo වගේ Hooks භාවිතා කරලා අනවශ්‍ය re-renders අවම කරගන්න පුළුවන්.

// ThemeProvider එකේ value එක memoize කරන ආකාරය
// src/ThemeContext.js (modified)
import React, { createContext, useState, useContext, useMemo, useCallback } from 'react';

export const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {},
});

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = useCallback(() => { // useCallback use කරලා function එක memoize කරනවා
    setTheme((currentTheme) => (currentTheme === 'light' ? 'dark' : 'light'));
  }, []); // Dependency array එක හිස් නිසා මේ function එක මුලින්ම හැදෙනවා විතරයි, re-render වලදී අලුතෙන් හැදෙන්නේ නැහැ.

  // useMemo use කරලා value object එක memoize කරනවා.
  // theme එක වෙනස් වුණොත් විතරයි මේ object එක අලුතෙන් හැදෙන්නේ.
  const contextValue = useMemo(() => ({
    theme,
    toggleTheme,
  }), [theme, toggleTheme]); // theme හෝ toggleTheme වෙනස් වුණොත් විතරයි re-calculate වෙන්නේ.

  return (
    <ThemeContext.Provider value={contextValue}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);

මේ වෙනසත් එක්ක, toggleTheme function එක හැම re-render එකකදීම අලුතෙන් හැදෙන්නේ නැහැ. ඒ වගේම contextValue object එක theme එක වෙනස් වුණොත් විතරයි අලුතෙන් හැදෙන්නේ. මේකෙන් performance issue එක තරමක් දුරට අඩු කරගන්න පුළුවන්.

හැබැයි මතක තියාගන්න, සරල සහ මධ්‍යම ප්‍රමාණයේ applications වලට Context API එක ගොඩක් හොඳ විසඳුමක්. හැම විටම Redux වගේ heavy library එකක් project එකට add කරන්න අවශ්‍ය වෙන්නේ නැහැ.

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

ඉතින් යාළුවනේ, අපි මේ tutorial එකෙන් React Context API එක සහ useContext hook එක ගැන ගොඩක් දේවල් ඉගෙන ගත්තා. Prop drilling කියන ගැටලුව මොකක්ද, ඒකට Context API එකෙන් කොහොමද විසඳුම් දෙන්නේ, වගේම Dark/Light Theme switcher එකක් deploy කරලා ඒක ප්‍රායෝගිකව ක්‍රියාත්මක වෙන හැටිත් අපි බැලුවා.

මතක තියාගන්න, Context API එක කියන්නේ React Developers ලට තියෙන ඉතාම ප්‍රබල tool එකක්. ඒක හරියට පාවිච්චි කළොත් ඔයාගේ application එකේ state management එක ගොඩක් සරල කරගන්න පුළුවන්. හැබැයි ඒක අනිසි විදිහට පාවිච්චි කළොත් performance issues එන්නත් පුළුවන්.

දැන් ඔයාට මේ concept එක හොඳට තේරෙන්න ඇති කියලා මම හිතනවා. මේක ඔයාගේ ඊළඟ React project එකේදී implement කරලා බලන්න. එතකොට තව දුරටත් මේක ගැන හොඳ අවබෝධයක් ලැබෙයි.

ඔයා මේ tutorial එක ගැන මොකද හිතන්නේ? ඔයාට මොනවා හරි ප්‍රශ්න තියෙනවා නම්, නැත්නම් ඔයාගේ අත්දැකීම් share කරන්න කැමති නම්, පහළින් comment එකක් දාගෙන යන්න!

එහෙනම් ආයෙත් මේ වගේම වැදගත් tutorial එකකින් හමුවෙමු! Stay tuned!