Handling Side Effects
Ketan Mayer-Patel
UNC Department of Computer Science
COMP 426: Modern Web Programming (Fall 2026)
Module 2: React
Lecture 10
Announcements
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
!
Recap
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
Clarifying when useState is run
AppComponent.ts
export const AppComponent = () => {
const [counter, setCounter] = useState(0);�
const plus = () => setCounter(counter + 1);
return (
<div>
<p>{ counter }</p>� <button onClick={plus}>� Increase count!� </button>
</div>
);
}
As we have seen, React works with this relationship.
UI / State Relationship
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Actions on the UI�changes the app’s state.
State changes then update the UI.
M2: React
L10
The UI
State
1
2
Loading External Data
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Let’s see an example in StackBlitz…
M2: React
L10
fetch(`url here`) � .then(res => res.json())� .then(data => {� // Work with data� }
Note: React components are NOT allowed to be async.
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Review: React Component Lifecycle
M2: React
L10
Whenever a re-render occurs, the entire React component function re-runs.
The Problem
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Actions on the UI�changes the app’s state.
State changes then update the UI.
M2: React
L10
The UI
State
1
2
Whenever the state changes, the UI is recalculated, calling the entire function body.
Whenever a re-render occurs, the entire React component function re-runs.
The Problem
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Actions on the UI�changes the app’s state.
State changes then update the UI.
M2: React
L10
The UI
State
1
2
Whenever the state changes, the UI is recalculated, calling the entire function body.
We may want to load some data from an external system when a component loads.�Then, have this persist on future re-renders.
Right now, a state is the only way for data to persist on re-render, but that causes an infinite re-rendering loop.
Any action that a component takes that is not rendering the UI (or updating state) is known as a side effect.
Side Effects
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Actions on the UI�changes the app’s state.
State changes then update the UI.
Component
M2: React
L10
The UI
State
1
2
Side Effect
There are three main cases when a side effect can be triggered.
Side Effects
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Every time a component renders.
State changes then update the UI.
Component
The first time a component renders.
When specific states change.
M2: React
L10
The UI
State
2
Side Effect
1
2
3
Handling Side Effects
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
useEffect(() => {
// Perform side effect!
});
useEffect Hook
Handling Side Effects
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
useEffect(() => {
// Perform side effect!
});
Run on every render
useEffect(() => {
// Perform side effect!
}, []);
Run on first render
useEffect(() => {
// Perform side effect!
}, [state]);
Run on state change
Handling Side Effects
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Let’s see an example in StackBlitz…�
M2: React
L10
useEffect(() => {
// Perform side effect!
});
Run on every render
useEffect(() => {
// Perform side effect!
}, []);
Run on first render
useEffect(() => {
// Perform side effect!
}, [state]);
Run on state change
useEffect takes in an optional dependency array.�If provided, useEffect only re-runs when dependencies (states) passed into the array change. If empty, the effect only runs once.
Examples of Side Effects
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
Cleanup
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
useEffect(() => {
const handler = () => { ... };
window.addEventListener(“scroll”, handler);�� return () => {� window.removeEventListener(“scroll”, handler);� }
}, []);
useEffect Hook
Ensures the event handler does not outlive the component!
Warning!!
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
Discuss in pairs�Consider this component to show a list of songs - and that I am a very indecisive listener! I flick through songs to find one I like.
Why might I report that flicking through songs appears to be very sluggish?
Submit thoughts to GradeScope!
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
M2: React
L10
Attendance Check!
!
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const curatedSongs = filterAndSortSongs(songs);
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>
{curatedSongs.map((song) => (
<div � key={song.id}
onClick={() => setSelectedSong(song)}
>
{ song.name }
</div>
))}
</>;
};
SongList.ts
We could try to fix this using a useState hook...
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Solution?
M2: React
L10
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const curatedSongs = filterAndSortSongs(songs);
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>
{curatedSongs.map((song) => (
<div � key={song.id}
onClick={() => setSelectedSong(song)}
>
{ song.name }
</div>
))}
</>;
};
SongList.ts
We could try to fix this using a useState hook...
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Solution?
There is a problem here…
M2: React
L10
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const [curatedSongs, setCuratedSongs]
= useState<Song[]>(filterAndSortSongs(songs));
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>...</>;
};
SongList.ts
Now, the value of curatedSongs is not re-calculated on every rerender.
What if songs changes?
We could try to fix this using a useState hook...
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Solution?
There is a problem here…
If our list of songs changes, we want to recalculate the filtering and sorting.
Any thoughts on how we can do this when the list of songs change?
M2: React
L10
function Playlist() {
...
return <SongList songs={playlist.songs} />
}
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const [curatedSongs, setCuratedSongs]
= useState<Song[]>(filterAndSortSongs(songs));
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>...</>;
};
SongList.ts
We could try to fix this using a useState hook...
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
Solution?
There is a problem here…
If our list of songs changes, we want to recalculate the filtering and sorting.
Any thoughts on how we can do this when the list of songs change?
M2: React
L10
SongList.ts
function Playlist() {
...
return <SongList songs={playlist.songs} />
}
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const [curatedSongs, setCuratedSongs]
= useState<Song[]>(filterAndSortSongs(songs));
useEffect(() => {
setCuratedSongs(filterAndSortSongs(songs));
}, [songs]);
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>...</>;
};
Preserving (caching) the result of an “expensive operation” every re-render and only recalculating it when needed is known as memoization.
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
BONUS
M2: React
L10
SongList.ts
Very common optimization across various types of software engineering projects!
function Playlist() {
...
return <SongList songs={playlist.songs} />
}
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const [curatedSongs, setCuratedSongs]
= useState<Song[]>(filterAndSortSongs(songs));
useEffect(() => {
setCuratedSongs(filterAndSortSongs(songs));
}, [songs]);
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>...</>;
};
Preserving (caching) the result of an “expensive operation” every re-render and only recalculating it when needed is known as memoization.
Handling Side Effects
COMP 426: Modern Web Programming (Fall '26)
BONUS
M2: React
L10
SongList.ts
React has a special hook called useMemo that helps combine the goals of using useState and useEffect in one hook.
function Playlist() {
...
return <SongList songs={playlist.songs} />
}
function SongList({ songs }: { songs: Song[] }) {
// Assume that the time complexity of this
// operation is around O(N^3).
const curatedSongs = useMemo(() => {
return filterAndSortSongs(songs);
}, [songs]);
const [selectedSong, setSelectedSong]
= useState<Song | null>(null);
return <>...</>;
};
Example by Claude
https://go.unc.edu/426f26-L10-useMemo
Create a small React application intended to be run on StackBlitz as a teaching example. The application should present a listing of songs using a SongList component that accepts an array of Song objects and a filtering function as props. SongList should apply the filtering function to the list and produce a scrollable listing of the results. The SongList component should have a React state variable keeping track of a selected song which is updated when a particular Song in the listing is clicked on. The filtering function should take an artificially long amount of time such that it will be noticeable if performed on each rerender of SongList when a Song is selected. The lesson to be taught is about using the useMemo hook to cache expensive results that should only be recalculated if the filter options are changed.