0% found this document useful (0 votes)
2 views13 pages

React Complete Notes

The document provides lecture notes on React performance optimization and managing shared state. It discusses techniques like React.memo, useCallback, and useMemo to address common performance issues, as well as strategies for managing complex state using useReducer and useContext, along with an introduction to the Zustand library for global state management. Key insights include avoiding unnecessary re-renders, function identity issues, and the benefits of memoization in React applications.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views13 pages

React Complete Notes

The document provides lecture notes on React performance optimization and managing shared state. It discusses techniques like React.memo, useCallback, and useMemo to address common performance issues, as well as strategies for managing complex state using useReducer and useContext, along with an introduction to the Zustand library for global state management. Key insights include avoiding unnecessary re-renders, function identity issues, and the benefits of memoization in React applications.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

React — Complete Lecture Notes

Lecture 24: Performance Optimization | Lecture 25: Managing Shared State


Covers: [Link] • useCallback • useMemo • useReducer • useContext • Zustand

LECTURE 24 — Performance Optimization in React


React applications can suffer from poor performance when components re-render more than they
need to, when functions are unnecessarily recreated, or when expensive calculations run on every
single render. React provides three built-in tools to address each of these problems: [Link],
useCallback, and useMemo.

1. The Three Core Performance Problems

Problem 1 — Unnecessary Component Re-renders


By default, React re-renders a child component every time its parent re-renders — even if the props
passed to that child have not changed at all. This means that a child displaying a static name will
still re-render every time an unrelated piece of state in the parent changes (for example, a color
picker or a counter). These re-renders are wasted work because the output of the child would be
exactly the same.

Problem 2 — Function Re-creation on Every Render


In JavaScript, functions defined inside a component body are brand-new objects every time the
component runs. So even if the logic of the function is identical, the reference to it changes on
every render. This is a serious problem when such functions are passed as props to child
components, because a child that is trying to avoid re-renders (via [Link]) will see a 'new'
prop value on every render and re-render anyway — completely defeating the memoization.

Problem 3 — Expensive Computations on Every Render


Some components need to perform heavy calculations — for example, looping through thousands
of records, filtering large arrays, or performing mathematical aggregations. By default, these
calculations run from scratch on every render, even when the data they depend on has not
changed. For large inputs, this can cause noticeable lag in the UI.

Key Insight: These three problems have a natural cascade. Solving Problem 1 with [Link] can
expose Problem 2 (function identity). Solving Problem 2 with useCallback addresses that, and
Problem 3 is independently addressed with useMemo.
2. The Three Solutions

2.1 — [Link]
[Link] is a Higher Order Component (HOC). A Higher Order Component is a function that
takes a component and returns a new, enhanced component. When you wrap a functional
component with [Link], React will perform a shallow comparison of the component's previous
and current props before deciding whether to re-render it.

Shallow comparison means React checks whether each prop value is the same reference (for
objects/arrays/functions) or the same primitive value (for strings, numbers, booleans). If none of the
props have changed, React skips the render entirely and reuses the last rendered output.

Syntax — how to apply [Link]:


const Employee = ({ name, hours, updateListTitle }) => {
// component code here
return (
<>
{/* UI goes here */}
</>
);
};

export default [Link](Employee);

Notice that [Link] wraps the component at export time. The component itself is written
normally — [Link] is applied around it as a wrapper. From this point on, Employee will only
re-render if one of its props (name, hours, or updateListTitle) actually changes.

Important: [Link] only does a SHALLOW comparison. For primitive props like strings and
numbers, this works perfectly. For object or function props, the reference must be stable — otherwise
[Link] sees them as changed even if the content is the same.

2.2 — useCallback
useCallback is a React hook that memoizes a function. Instead of creating a brand new function
object on every render, useCallback returns the same function reference across renders — unless
one of its specified dependencies changes.

This is critically important when you pass a function as a prop to a child component wrapped in
[Link]. Without useCallback, a new function is created every render, the prop appears to
have changed, and [Link]'s protection is bypassed. With useCallback, the function reference
stays stable, and [Link] works correctly.

Syntax:
const EmployeeList = () => {
const [title, setTitle] = useState('');

// Without useCallback: new function created every render


// const updateTitle = (str) => { setTitle(str); };

// With useCallback: same reference unless deps change


const updateTitleMemoized = useCallback(
(str) => { setTitle(str); },
[] // empty array = never recreated after mount
);

return (
<>
<Employee updateListTitle={updateTitleMemoized} />
</>
);
};

The second argument to useCallback is the dependency array. The function is only recreated when
a value in this array changes. An empty array [] means the function is created once on mount and
never recreated. If the function uses a state variable (e.g., count), that variable should be in the
dependency array so the function always has access to the latest value.

Warning: Do not overuse useCallback. Every memoized function consumes memory and adds
complexity. Only apply it when you have measured a real performance issue — typically when the
function is passed to a memoized child component.

2.3 — useMemo
useMemo is a React hook that memoizes the result of a computation. It runs the provided function
once and caches the return value. On subsequent renders, instead of re-running the calculation,
React returns the cached value — unless one of the specified dependencies has changed.

useMemo is also useful when passing computed values (especially objects or arrays) as props to
child components. Without it, a new array or object is created on every render, which again breaks
[Link]'s shallow comparison. With useMemo, the reference stays stable.
Syntax and example:
const Employee = ({ hours }) => {

const computePoints = (n) => {


let points = 0;
for (let i = 1; i < n; i++) {
points += i * i; // heavy loop
}
return points;
};

// Without useMemo: computePoints(hours) runs every render


// With useMemo: only re-runs when 'hours' changes
const memoizedPoints = useMemo(
() => computePoints(hours),
[hours] // dependency: recompute only when hours changes
);

return <div>Points: {memoizedPoints}</div>;


};

For large values of hours (hours = 1,000,000), the loop would take significant time. Without
useMemo, this runs on every render — even if hours hasn't changed. With useMemo, the result is
cached and only recomputed when hours actually changes.

Warning: Like useCallback, useMemo adds memory overhead. Don't apply it to cheap computations.
Only use it when profiling shows a real bottleneck.

3. When to Use vs. When NOT to Use Memoization

USE memoization when... AVOID memoization when...


Noticeable performance issues exist Component is small or cheap to render
Child re-renders due to unchanged props Computations are simple or fast
([Link])
Passing functions to memoized child components Applied without measuring performance first
(useCallback)
Performing expensive computations or data It adds complexity without clear benefit
processing (useMemo)
Passing computed arrays/objects as props The component rarely re-renders anyway
(useMemo)

4. The Full Optimization Cascade — Step by Step

The slides walk through a real-world example with EmployeeList (parent) and Employee (child).
This example is crucial for understanding how the three tools interact.

Step 1 — The Problem: No Optimization


EmployeeList re-renders when color changes or title changes. Every time it re-renders, all
Employee child components also re-render — even if their individual data (name, hours) hasn't
changed. Additionally, any function defined inside EmployeeList (like updateTitle) is recreated on
every render.

Step 2 — Apply [Link] to Employee


export default [Link](Employee);

Now Employee only re-renders if its props change. Problem partially solved — but a new issue
emerges.

Step 3 — New Problem: Function Identity


// Inside EmployeeList:
const updateTitle = (str) => {
setTitle(str);
};

EmployeeList passes updateTitle as a prop to Employee. But since updateTitle is defined inside
EmployeeList without useCallback, a brand-new function object is created on every render.
[Link] compares the old prop (old function reference) with the new prop (new function
reference) — they are different, so Employee re-renders anyway. [Link] is effectively useless
here.

Step 4 — Apply useCallback to the function


const memoizedUpdateTitle = useCallback(updateTitle, []);

Now the same function reference is passed to Employee on every render. [Link]'s shallow
comparison sees no change in the prop, and Employee correctly skips re-rendering.

Step 5 — Another Problem: Expensive Computation inside Employee


Inside Employee, computePoints(hours) runs a heavy loop. Even though Employee now correctly
avoids unnecessary re-renders, when it does render (e.g., when hours changes), the computation
still runs needlessly on sub-renders triggered by other causes.
Step 6 — Apply useMemo to the computation
const memoizedPoints = useMemo(
() => computePoints(hours),
[hours]
);

Now the computation only runs when hours actually changes. All three problems are solved.

Final Optimized State: [Link](Employee) avoids unnecessary renders •


useCallback(updateTitle) keeps the function reference stable • useMemo(computePoints) avoids
repeated heavy computation

5. Quick Reference Summary — Lecture 24

Tool What It Memoizes Best Used When


[Link] A whole component's output Child re-renders due to
unchanged props

useCallback A function reference Passing functions as props to


memoized children

useMemo A computed/returned value Expensive calculations or stable


object/array props

LECTURE 25 — Managing Shared State in React


As React applications grow, sharing state between components that are not directly related
becomes a challenge. This lecture covers two approaches: using React's built-in useReducer +
useContext combination, and using the external Zustand library.

1. Prop Drilling — The Problem


Prop drilling refers to the pattern where data (props) must be passed from a parent component all
the way down through multiple intermediate components in the tree — even though those
intermediate components don't actually need or use the data themselves. They're just passing it
along to reach a deeply nested child.
Example: If App has state that only a deeply nested GrandchildComponent needs, you'd have to
pass it through App → Parent → Child → GrandchildComponent. Parent and Child don't use the
data — they're just 'pass-through' layers.

Problems caused by prop drilling:


• Makes code harder to maintain — every intermediate component must accept and forward
props it doesn't care about
• Makes refactoring risky — renaming or changing a prop requires updating every layer it
passes through
• Creates unnecessary coupling between components

The solution is the Context API — specifically the useContext hook.

2. Managing Complex State with useReducer

2.1 — What is useReducer?


useReducer is a React hook for managing state in functional components. It works similarly to
useState, but is designed for more complex situations. Instead of calling setState(newValue)
directly, you dispatch an action — a plain object describing what you want to do — and a separate
reducer function handles how the state should change in response.

useReducer is especially useful when:


• The state is complex — an object or array with multiple related fields
• The next state depends on the previous state
• Multiple different kinds of state updates are possible (add, delete, update)
• You want state update logic centralized in one place for clarity and testability

2.2 — useState vs useReducer

useState useReducer
Best for simple, local state Best for complex state logic
e.g., a toggle, a counter, an input value e.g., a list of objects with CRUD operations
State update logic lives inline in handlers State update logic is centralized in reducer
function
Hard to share across components cleanly Easy to combine with useContext for global state
Less boilerplate More structured, more scalable
2.3 — The Four Key Elements of useReducer

1. initialState — The starting value of the state. Can be a primitive, object, or array.
const allEmployees = [
{ id: 1, name: 'Ahmad', hours: 10 },
{ id: 2, name: 'Bilal', hours: 20 },
];

const initialState = { employees: allEmployees };

2. Reducer Function — A pure function that takes the current state and an action object, and
returns the new state. It uses a switch statement on [Link] to handle each kind of update. A
pure function means it doesn't modify the original state — it always returns a new object.
const employeeReducer = (state, action) => {
switch ([Link]) {

case 'add':
return {
employees: [...[Link], [Link]],
};

case 'delete':
return {
employees: [Link](
(emp) => [Link] !== [Link]
),
};

case 'update':
return {
employees: [Link]((emp) =>
[Link] === [Link] ? [Link] : emp
),
};

default:
return state; // always return state in default case
}
};
3. State — The current value of the state returned by useReducer. Always reflects the latest
update.

4. Dispatch — A function used to send (dispatch) an action to the reducer. Calling dispatch triggers
the reducer, which computes and returns the new state.
// Setting up useReducer:
const [state, dispatch] = useReducer(employeeReducer, initialState);

// Dispatching actions:
dispatch({
type: 'update',
employee: { id: idToUpdate, name: fullName, hours: hrs },
});

dispatch({ type: 'delete', id: idToDelete });

dispatch({ type: 'add', employee: { id: 3, name: 'Sara', hours: 15 } });

Pattern: The action object always has a 'type' field (a string describing the operation) and optionally
carries data needed for the update (e.g., the employee object to add, or the id to delete).

3. Sharing State with useContext

3.1 — What is Context in React?


Context in React is a mechanism for sharing values across the component tree without manually
passing props at every level. It acts like a global store that any component in the tree can tap into
directly, without needing intermediate components to pass anything along.

Its main purpose is to eliminate prop drilling. Instead of threading state through five layers of
components, you put it in a Context, and any component that needs it can access it directly via
useContext.

3.2 — The Three Steps to Using Context

Step 1 — Create the Context


Use [Link]() to create a context object. This object has a Provider component built
into it.
const EmployeeContext = createContext();
Step 2 — Create a Provider Component
The Provider is a wrapper component that holds the shared state (using useReducer or useState)
and makes it available to all its children via the [Link]'s value prop.
const EmployeeProvider = ({ children }) => {
const [state, dispatch] = useReducer(employeeReducer, initialState);

return (
<[Link] value={{ state, dispatch }}>
{children}
</[Link]>
);
};

export { EmployeeProvider };

children is a special React prop that refers to all the components nested inside the Provider. By
passing state and dispatch as the value, any child anywhere in the tree can read the state and
dispatch actions.

Step 3 — Wrap the App and Consume the Context


// In [Link] — wrap components that need the shared state:
function App() {
return (
<EmployeeProvider>
<EmployeeList />
</EmployeeProvider>
);
}

// In any child component — consume the context:


const { state, dispatch } = useContext(EmployeeContext);

// Then dispatch just like before:


dispatch({ type: 'delete', id: idToDelete });

Key Point: Any component inside EmployeeProvider can call useContext(EmployeeContext) to get
both the state and the dispatch function — no prop drilling needed. Components outside the Provider
cannot access this context.
4. Zustand — A Simpler Alternative for Global State

4.1 — What is Zustand?


Zustand is a lightweight, external state management library for React. It provides a centralized
global store that any component can access directly — without needing a Provider wrapper, a
context object, a reducer function, or a dispatch call. Everything is defined in one place and
consumed via a simple hook.

'Zustand' is the German word for 'state'.

4.2 — Creating a Zustand Store


You create a store using the create() function from Zustand. Inside it, you define your state values
and the functions that update them (called actions). The set function provided by Zustand is used to
update the state.
import { create } from 'zustand';

const useStore = create((set) => ({


count: 0,
increase: () =>
set((state) => ({
count: [Link] + 1,
})),
}));

useStore is now a custom hook. The store holds count (initial value 0) and an increase action. The
set function takes a callback that receives the current state and returns the new state — similar to
how a reducer works but without the switch statement and action types.

4.3 — Using the Store in a Component


const Component = () => {
// Select only the state slices you need:
const count = useStore((state) => [Link]);
const increase = useStore((state) => [Link]);

return (
<button onClick={increase}>
Count: {count}
</button>
);
};
You pass a selector function to useStore to pick only the part of the state you need. This is
important for performance — the component only subscribes to the specific slice it selects and only
re-renders when that slice changes.

No Provider Needed: Zustand's store is global. You don't wrap your app in anything. Any component
anywhere can call useStore and access the state directly.

5. Zustand vs. useContext + useReducer — Full Comparison

Feature useContext + useReducer Zustand


Provider needed? Yes — must wrap tree No — none required

dispatch needed? Yes — for all updates No — call functions directly

Boilerplate High — context, reducer, provider Low — single store

External library? No (built into React) Yes (npm install zustand)

Re-render behavior All consumers re-render on any Only subscribed slice consumers
context change re-render

State access useContext hook inside Provider useStore hook anywhere in app
tree

Best for Full control, no extra Simplicity, minimal setup


dependencies

5.1 — The Critical Re-rendering Difference


This is an important exam point. The re-rendering behavior differs significantly between the two
approaches:

• When ANY value in the Context Provider's value prop changes, ALL components
consuming that context will re-render — even if they only use a part of the state that
didn't change. For example, if state has both employees and color, and color
changes, every component using useContext(EmployeeContext) re-renders, even
those that only care about employees.

• Components subscribe to specific slices of state via selectors. If count changes but
a component only subscribes to name, that component does NOT re-render. This
makes Zustand significantly more efficient in large apps with many pieces of global
state.
6. Complete Summary — Both Lectures

Concept What It Solves Key Syntax


[Link] Child re-renders when props export default
unchanged [Link](Component)

useCallback New function reference on every useCallback(fn, [deps])


render

useMemo Expensive recomputation every useMemo(() => compute(),


render [deps])

useReducer Complex state with multiple const [state, dispatch] =


update types useReducer(fn, init)

useContext Prop drilling across many const val =


component layers useContext(MyContext)
Zustand Global state without Provider const x = useStore(state =>
boilerplate state.x)

Remember for your exam: [Link] = memoize a COMPONENT | useCallback = memoize a


FUNCTION | useMemo = memoize a VALUE | useReducer = structured state with reducer |
useContext = share state without prop drilling | Zustand = global state, no Provider, no dispatch,
smarter re-renders

You might also like