PHASE 2 · INTERMEDIATE · LESSON 1 OF 4

// advanced_react_patterns.tsx Advanced React Patterns

ደረጃ: Intermediate–Advanced
4 ዋና Patterns
Real-world Code
When to use what

በጀማሪ ደረጃ hooks ምንድን ናቸው የሚለውን አይታችኋል — በቀጣዩ ደረጃ ግን የትኛው መቼ እና ለምን እንደሚያስፈልግ፣ እንዲሁም የየራሳቸው Trade-offs ምን እንደሆኑ እንማራለን። በእውነተኛ (Real) codebase ላይ ምን አይነት ችግሮች ያጋጥማሉ? እንዴትስ ትፈቷቸዋላችሁ?

❌ BEGINNER THINKING
➤ useStateን በሁሉም ቦታ መጠቀም
➤ Propsን ለሁሉም component ማስተላለፍ
➤ ሁሉንም logic በ useEffect ውስጥ ማስቀመጥ
➤ Re-render መቼ እንደሚፈጠር አለማወቅ
✅ ADVANCED THINKING
➤ Stateን በትክክለኛው ቦታ ማስቀመጥ
➤ Prop drilling vs Context vs Zustand መለየት
➤ Custom hooks — logicን መነጠል
➤ Re-renderን የሚቀንሱ ነገሮችን ማወቅ
✍️ ያዘጋጀው: @AppMinds_ET | EthioCode Phase 2
PATTERN 01 Custom Hooks — Logicን መለየት useXxx()

Custom Hook ሎጂክን (Logic) ከ component ውስጥ በማውጣት በተለያየ ቦታ በድጋሚ (reuse) እንድንጠቀመው ያደርጋል! Component የ UI ስራ ብቻ ሲሰራ፣ ሎጂኩን ደግሞ Hook ይይዘዋል።

ጀማሪ ስትሆኑ component ውስጥ ሁሉንም ነገር አንድ ላይ ትፅፉ ነበር — አሁን ግን Separation of Concerns (የስራ ክፍፍል) ያስፈልጋል። UIን እና Logicን ለያይቷቸው!

⚠️
ዋናው ጥያቄ — መቼ ነው Custom Hook የሚያስፈልጋችሁ? ① አንድን logic 2 እና ከዚያ በላይ components ሲጠቀሙበት — DRY! ② Component መጠኑ በዝቶ ለማንበብ ሲያስቸግር — ሎጂኩን ለዩት! ③ ሎጂኩን ለብቻው test ለማድረግ — hook ስራን ያቀላል!
UserProfile.jsx ❌ BEFORE
// ❌ Logic እና UI አንድ ላይ ተቀላቅሏል!
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);
  // 👆 በሌላ ቦታም ይህንኑ ደጋግማችሁ ትፅፋላችሁ
  if (loading) return <Spinner />;
  if (error) return <Error />;
  return <div>{user.name}</div>;
}
useUser.js + Profile.jsx ✅ AFTER
// ✅ Custom Hook — logic ተለይቷል
function useUser(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    /* fetch logic ሁሉ እዚህ ውስጥ ነው */
  }, [userId]);
  return { user, loading, error };
}

// Component — የ UI ስራ ብቻ!
function UserProfile({ userId }) {
  const { user, loading, error }
    = useUser(userId); // ✅ clean!
  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}
useFetch.ts — Generic reusable hook
// ✅ Generic useFetch — ለማንኛውም endpoint የሚሰራ!
function useFetch<T>(url: string) {
  const [data, setData]     = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError]   = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false; // ← Cleanup! Race conditionን ያስቆማል

    const fetchData = async () => {
      try {
        setLoading(true); setError(null);
        const res = await fetch(url);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json = await res.json();
        if (!cancelled) setData(json); // component unmount ሲሆን stateን አትቀይሩ
      } catch (e) {
        if (!cancelled) setError(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    fetchData();
    return () => { cancelled = true; }; // cleanup!
  }, [url]);

  return { data, loading, error };
}

// ► Usage — ከማንኛውም endpoint ጋር መጠቀም ይቻላል
const { data: users }  = useFetch<User[]>('/api/users');
const { data: posts }  = useFetch<Post[]>('/api/posts');
const { data: product }= useFetch<Product>(`/api/products/${id}`);
💡
በእውነተኛ ፕሮጀክቶች የሚያጋጥም ችግር — Race Condition ተጠቃሚው ከ id 1 ወደ 2 በፍጥነት ሲቀይር — የ id 2 request ሳያልቅ የ id 1 ዳታ ከመጣ state ይበላሻል! let cancelled = false የሚለው አፃፃፍ (pattern) ይህንን ይከላከላል።
PATTERN 02 Re-render Optimization memo · useMemo · useCallback

ዋናው ጥያቄ — React መቼ ነው re-render የሚያደርገው? State ወይም Props ሲቀየሩ። ችግሩ ምንድን ነው? Parent ሲቀየር (re-render ሲያደርግ) የውስጡ Children ሁሉም re-render ያደርጋሉ! ይህ ሁልጊዜ አስፈላጊ ስላልሆነ መቆም አለበት።

🚨
ትልቅ Mistake — Premature Optimization! useMemo/useCallbackን በሁሉም ቦታ አትጠቀሙ! የ Performance ችግር ሲያጋጥማችሁ ብቻ ተጠቀሙ። መጀመሪያ ለኩ (Measure) — Optimization ቀጥሎ ይመጣል።
optimization.tsx — When to use each
// ── React.memo — Componentን wrap ያደርጋል። Props ካልተቀየሩ በስተቀር re-render አያደርግም።
const ExpensiveList = React.memo(({ items }) => {
  // ① items prop ካልተቀየረ — re-render አያደርግም
  return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
});

// ── useMemo — ከባድ ካልኩሌሽንን cache ለማድረግ
function Dashboard({ orders }) {
  // ❌ Bad: ሁልጊዜ re-render ሲሆን በድጋሚ ይሰላል!
  const total = orders.reduce((s, o) => s + o.amount, 0);

  // ✅ Good: orders ሲቀየሩ ብቻ ይሰላል
  const total = useMemo(
    () => orders.reduce((s, o) => s + o.amount, 0),
    [orders] // ← dependency
  );

  // ── useCallback — Function referenceን stable (የማይቀየር) ያደርጋል
  // ❌ Bad: ሁልጊዜ አዲስ function ይፈጠራል! ይህም memoን እንዳይሰራ ያደርገዋል!
  const handleClick = (id) => deleteOrder(id);

  // ✅ Good: stable reference — memo works!
  const handleClick = useCallback((id) => deleteOrder(id), []);

  return <ExpensiveList items={orders} onDelete={handleClick} />;
}
🔄 Re-render Visualizer
LIVE

State ቀይሩ — የትኞቹ components re-render ያደርጋሉ? ብርቱካናማ ብልጭታ (Orange flash) = re-render መሆኑን ያሳያል።

PATTERN 03 State Management — Prop Drilling ➜ Context ➜ Zustand when to upgrade

Prop Drilling ማለት Propsን በ component ሰንሰለት ውስጥ ለሁሉም ማስተላለፍ ነው። እስከ 2–3 እርከን (levels) ማስተላለፍ ችግር የለውም፤ 5+ እርከን ሲሆን ግን ችግር ነው! ስለዚህ Context ወይም እንደ Zustand ያሉ External state toolsን ተጠቀሙ።

Prop Drilling ምን እንደሚመስል — Visual:

❌ PROP DRILLING
App user={user}
Layout user={user} ← ባያስፈልገውም ያልፋል
Sidebar user={user} ← ባያስፈልገውም ያልፋል
Avatar user={user} ← የሚፈልገው ይህ ብቻ ነው
✅ CONTEXT
UserContext.Provider
Layout no props
Sidebar no props
Avatar useContext ✅
state-decision.md — When to use what?
┌─────────────────────────────────────────────────┐
│           STATE MANAGEMENT DECISION TREE        │
└─────────────────────────────────────────────────┘

useState  → Local state ነው። ለአንድ component ብቻ ተጠቀሙበት።
            ex: form input፣ toggle፣ modal open/close

Context   → Global state ነው። ለ Theme፣ Auth user፣ Language ይጠቅማል።
            ⚠️ ቶሎ ቶሎ የሚቀያየር (Frequently changing) state አታስቀምጡበት!
            (ምክንያቱም የተጠቀሙትን ሁሉ re-render ያደርጋል!)

Zustand   → ለተወሳሰበ global state ያገለግላል። Actions፣ async ነገሮች ሲኖሩ፣
            Context አቅም ሲያንሰው እና Performance ወሳኝ ሲሆን ተጠቀሙበት።

React Query → ለ Server state፣ API data fetching፣
             Caching እና loading states ተጠቀሙበት።

────────────────────────────────────────────────
RULE: ሁልጊዜ ከቀላሉ ጀምሩ።
  useState → Context → Zustand → Redux
  ከመነሻው ወደ ከባዱ አትዝለሉ!
⚠️
በ Context ላይ የሚሰራ ትልቁ ስህተት — ቶሎ ቶሎ የሚቀየር State ማኖር! Theme (dark/light) ➜ Context ✅ — ቶሎ ቶሎ አይቀያየርም። Shopping cart items ➜ Context ❌ — ቶሎ ቶሎ ስለሚቀያየር፣ የተጠቀሙትን ሁሉ re-render ያደርጋል! ለእንደዚህ አይነት Zustand/Jotai የተሻሉ ናቸው።
PATTERN 04 Compound Components Pattern flexible API design

Compound Components ማለት የተለዩ ነገር ግን አብረው የሚሰሩ components ማለት ነው። የ HTMLን <select> እና <option> አስቡ — አብረው ነው የሚሰሩት። በእውነተኛ ፕሮጀክቶች ለ Select፣ Tabs፣ Accordion፣ እና Menu እንጠቀምበታለን።

Tabs.compound.tsx — Compound Pattern
// ── Compound Tabs Component ─────────────────────
const TabsContext = createContext({});

function Tabs({ children, defaultTab }) {
  const [active, setActive] = useState(defaultTab);
  return (
    <TabsContext.Provider value={{ active, setActive }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function Tab({ id, children }) {
  const { active, setActive } = useContext(TabsContext);
  return (
    <button
      className={active === id ? 'active' : ''}
      onClick={() => setActive(id)}
    >{children}</button>
  );
}

function TabPanel({ id, children }) {
  const { active } = useContext(TabsContext);
  return active === id ? <div>{children}</div> : null;
}

// Sub-components ማያያዝ!
Tabs.Tab      = Tab;
Tabs.TabPanel = TabPanel;

// ── አጠቃቀም — ንፁህ እና Flexible የሆነ API ───────────────
function Dashboard() {
  return (
    <Tabs defaultTab="overview">
      <div className="tab-list">
        <Tabs.Tab id="overview">Overview</Tabs.Tab>
        <Tabs.Tab id="analytics">Analytics</Tabs.Tab>
        <Tabs.Tab id="settings">Settings</Tabs.Tab>
      </div>
      <Tabs.TabPanel id="overview"><Overview /></Tabs.TabPanel>
      <Tabs.TabPanel id="analytics"><Analytics /></Tabs.TabPanel>
    </Tabs>
  );
}
ለምን Compound Pattern? — 3 ዋና ዋና ጥቅሞች ① Flexible ነው — ተጠቃሚዎች (Developers) እንደፍላጎታቸው ያቀናብሩታል። ② Props አይበዙም — ለአንድ component 20 props ከማስተላለፍ ያድናል። ③ ለማንበብ ቀላል ነው — መዋቅሩ (Structure) በግልፅ ስለሚታይ ኮዱ ምን እንደሚሰራ በቀላሉ ይነግረናል።
LAB Interactive Patterns Lab hands-on
⚡ Patterns Lab
INTERACTIVE
Custom Hooks
Memo Test
Context

የ Custom hook ሙከራ — useLocalStorage hookን ሞክሩ!

← useLocalStorage hook simulator
function useLocalStorage(key, initial) {
  const [val, setVal] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initial;
  });
  const set = (v) => { setVal(v); localStorage.setItem(key, JSON.stringify(v)); };
  return [val, set];
}

State update ሲሆን የትኞቹ components re-render ያደርጋሉ? Count ሲቀየር ሁሉም re-render ያደርጋሉ ወይስ memo ያስቆማቸዋል?

WITHOUT memo
WITH React.memo
← ቁልፍ ተጫኑ → re-renders መኖሩን ያሳያችኋል

የ Context Provider ሙከራ! Value ቀይሩ — ሁሉም Consumers ጋር ይቀየራል!

SCENARIO በእውነተኛ ፕሮጀክቶች (Real Codebase) ላይ ምን ችግር ያጋጥማል? production bugs
PRODUCTION BUG E-Commerce App — Cart ሲቀየር ሙሉው Page ይንቀራፈፋል (ይቀዘቅዛል)
ሁኔታ: ተጠቃሚው Product ሲጨምር ሙሉው Page ይቆማል።
ምን ተፈጠረ? ሁሉም components ለ CartContext ሰብስክራይብ ስላደረጉ፣ የ Cart state ሲቀየር — Header፣ Sidebar፣ ProductList እና Footer ሁሉም re-render ያደርጋሉ።
መፍትሄ: የ Cart stateን ነጥሎ በማውጣት Zustandን ተጠቀሙ። Components ዳታውን ሲፈልጉ ብቻ Subscribe ያደርጋሉ። ይህም አላስፈላጊ re-rendersን በ 95% ይቀንሳል!
INTERVIEW Q Senior React የሥራ ቃለመጠይቆች (Interviews) — ሁልጊዜ የሚጠየቁ
Q1: "የ useEffect dependency array ባዶ [] የሚሆነው መቼ ነው?"
→ Component ለመጀመሪያ ጊዜ mount ሲሆን (አንዴ ብቻ እንዲሰራ ስንፈልግ) ለምሳሌ API call ለማድረግ። ነገር ግን Stale closure እንዳይፈጠር ተጠንቀቁ!

Q2: "በ useMemo እና useCallback መካከል ያለው ልዩነት ምንድን ነው?"
→ useMemo ቫልዩን (value) cache ያደርጋል፣ useCallback ደግሞ ፈንክሽንን (function) cache ያደርጋል።

Q3: "አንድ component 15 Props ቢኖሩት ምን ታደርጋላችሁ?"
→ Componentን መከፋፈል፣ Custom hook መጠቀም፣ Compound pattern መጠቀም ወይም Config objectን መላክ ይቻላል።
TEST Real-World Quiz
❓ ይህ code ምን ችግር አለው?
function SearchResults({ query }) { const results = useMemo( () => items.filter(i => i.name.includes(query)), [] // ← dependency array! ); return <List items={results} />; }
AuseMemoን ተጠቅመዋል — አያስፈልግም
BDependency array ላይ ስህተት አለ። query ይጎድለዋል — ይህም ወደ stale results ያመራል! ✅
Citemsን እንደ prop አላሳለፉም
Dfilterን በ useEffect ውስጥ ማስቀመጥ ነበረበት