- Lab
- Core Tech

Guided: Optimization in React
In this guided lab, Optimization in React, you will learn to diagnose and mitigate wasted renders, implement memoization strategies using useCallback and useMemo, and optimize context and prop handling to improve your app's efficiency. You’ll also explore advanced concepts such as managing external API connections and leveraging React.memo for fine-tuned render control. Whether you’re aiming to boost your app’s responsiveness or deepen your understanding of React’s performance tools, this lab provides essential skills and insights for writing high-performance applications.

Path Info
Table of Contents
-
Challenge
Overview
Welcome!
In this guided lab, you will optimize a simple helpdesk application to fix performance issues. As you perform these tasks, you'll learn how to diagnose issues, reduce wasted renders, and fix common React performance problems.
What to Expect
You should have foundational experience with React and have built a full application before.
info> The React Foundations lab series will teach you the fundamentals and uses the same Helpdesk demo application.
The JavaScript code in the lab is written in idiomatic React, which is typically ES2015+ syntax. You should be able to work with arrow functions, Promises, ES modules, and let/const.
Additionally, you should be passingly familiar with the React Developer Tools and ideally have used the Profiler feature in both the browser's developer tools and React Developer Tools.
info> Never used the React Dev Tools? Watch the 3-minute tour in the React Debugging Playbook course. To get more familiar with the browser and React profiling tools, watch the Conducting Performance Audits module in the React Performance Playbook course.
info> Stuck on a task? Check out the
solutions/
folder in the project directory for full solution files and reference code for each step. Files with numbers (likeindex.2.jsx
) correspond to the task order in the step.Getting Started
To make sure the lab environment is set up and working, you'll start by running the Globomantics Helpdesk demo application. Once set up, you can visit {{localhost:5173}} to view the app in your browser, or in the Web Browser tab in the editor to the right.
info> Web browser not working? Sometimes while reading through the lab, the dev server will shut down and you'll have to reconnect to the Terminal. Just run
npm run dev
again to start the server back up. Other times, the web browser may not always reflect the latest code due to the auto-save feature of the lab. Just click the refresh button next to the address bar in the tab to force-refresh the page.How is the app hosted?
You may wonder what "VITE" means and how the app is being hosted on the above URL. This comes from the JavaScript development tool Vite.js.Vite is a frontend tool that bundles and builds JavaScript applications, including React. There are other options available depending to build and host React applications depending on what you need, such as Next.js.
Vite is versatile and supports many other frameworks and libraries, including plain HTML and CSS with very little configuration.
A Simple Helpdesk App
The application is a simple helpdesk where you can submit and view support tickets related to fictitious Globomantics products.
A quick tour around the codebase:
src/components
holds different components that are on the pagesrc/hooks
holds a custom hook to work with IndexedDB, the web-based storage engine and the idb packagesrc/contexts
hold a shared context to share state in the appApp.jsx
is the main component that renders the page- The
TicketForm
component encapsulates the form fields and handling form submission - The
TicketDisplay
component handles displaying ticket information to the user - The
TicketList
component handles displaying the list of tickets in the database
Additionally, the file tree has been organized into modules within the
src
folder so that it's easier to focus on the code you'll write throughout the lab.Scale exposes hidden problems
Ticket data is stored in IndexedDB which is created when you run the app for the first time.
If you run the app without any ticket data, you are unlikely to notice any performance issues. This is common in web development -- problems don't start appearing until you hit a certain level of scale with your application, and usually it's data or state management that exposes problems in a client-side React application. Once that happens, it becomes crucial to know how to diagnose and debug problems.
You are not your end-user
For this lab, the database is seeded with 10,000 tickets to exacerbate any performance issues. Depending on your local machine, the performance issues may be significant, or they may be minor. Remember though, you are not your end-user and they are likely to experience performance problems well before you.
Poor performance impacts the business
Performance issues can impact the user experience which can impact business metrics. The director who hired you on as a consultant told you so:
Senior Director: These performance problems are impacting our "Time to Fix" support metrics. The support organization is losing approximately 90 hours a month to performance degradation, lowering our ticket response times, and we won't be able to budget for additional headcount unless we turn this metric around.
Time's a wasting, get going!
-
Challenge
Optimizing Reference Handling
Optimizing Reference Handling
Where do you even start when diagnosing performance problems?
Your eyes!
When diagnosing performance issues, pretend you're Sherlock Holmes. Notice everything -- it could be important!
Sometimes issues are observable to the naked eye without any special tools and those can be the easiest to tackle first.
The case of the curious console
Go ahead and open the browser's Developer Tools (
F12
) and see what the console is showing.Do you see something curious?
There are two of the same exact messages:
Successfully connected to IndexedDB
Plus an error:
DOMException: Key already exists in the object store.
If you follow the message stack trace, you will find yourself in the
src/hooks/useTicketDb.jsx
file.Running effects on mount
The console message originates in a Promise
then
method, after successfully connecting to IndexedDB.openDB(...).then( (result) => { console.info("Successfully...", result); return seedDb(result).then(() => setDb(result)); });
Using the powers of deduction, you believe this means that
openDB
is being called twice -- and you'd be right. But why?The
useEffect
has an empty dependency array ([]
) which means it only runs once on mount. Shouldn't the effect only be run once during the lifetime of the app?That depends on where the hook is being used.
Hook instances
After searching around, you identify the
useTicketDb
hook is being used in two places:src/App.jsx
src/components/TicketList.jsx
Used in two places and two console log messages? A mere coincidence? Or a conspiracy?
Turns out this is by design. A React hook is a function which means if you invoke it in multiple components, it will run its effects each time. Each time you use a hook, you get a new instance of it.
Controlling object lifetime
Normally this is acceptable and expected behavior but other times, like when connecting to a database, it's not desirable. You don't want to call
openDB
twice, you want to initialize a single reference to the database and maintain its lifetime over the app.To address this, you will need to lift state up -- but where should it go? The hook keeps a
useState
for thedb
instance. Can it go any higher? You could store thedb
reference in a Context which is only initialized once but in this case, it's probably easier to lift state into the module scope.Leveraging module state
An ES module is only loaded once by default for the lifetime of an application. This means any variables in the top-level scope are maintained and only initialized once.
The goal is to wait for the DB connection process to complete and only connecting once, while maintaining a single
db
instance.openDB
returns a Promise, which can be stored in module state. This allows each hook instance to "wait for" the Promise to resolve. TheopenDB
Promise will set adb
variable in module state so only one instance is maintained.Since setting module state variables will not "notify" React that the DB connection is ready, you will also need an
initialized
state within the hook to trigger a re-render. If you reload the application and check the browser console, you should only see one message:Successfully connected to IndexedDB
Conveniently, fixing the multiple
db
instances also fixed the red error message! Often in performance work, it's best to tackle one issue at a time as issues can be linked together. In this case, since theseedDb
function was being called twice, there was a race condition in seeding the database rows with auto-incrementing IDs. This is also why you may have noticed there are 20,000 tickets, not 10,000 like originally intended.The usefulness of magnifying glasses
What can't be seen by the naked eye requires special tools. In detective work, it might be the iconic magnifying glass, fingerprint testing, or surveillance footage.
For React performance debugging, there are several useful tools:
- A code linter
- An interactive debugger
- The React Developer Tools
- Browser script profiling
- Google Lighthouse
- WebPageTest
In this lab, you'll become familiar with the first four tools. The rest you can learn outside of this as part of general web performance work.
Linting your code
ESLint is a code-based tool that inspects your React app for common issues. These issues can lead to performance problems and should usually be addressed.
Go ahead and run the lint command to see what unaddressed issues may be lurking in the codebase. Ah-hah! There are two issues that need to be fixed.
-
Challenge
Optimizing Hook Dependencies
Optimizing Hook Dependencies
ESLint uses different "rules" to lint your code and they are noted on the right-side of the messages.
The two warnings appear to be coming from the
react-hooks/exhaustive-deps
rule:React Hook useEffect has a missing dependency
React hooks should declare all their dependencies on external variables in their dependency array. It's easy to miss this, so the lint rule checks to see if you exhaustively list the hook's dependencies.
Fixing
exhaustive-deps
violationsThe first warning suggests fixing the issue by adding
getAllTickets
to the hook dependency array in theTicketList
component.Why don't you try it out? If you reload the page... uh-oh, something weird is going on. The ticket list keeps loading in an infinite loop!
Debugging infinite hooks
Since the only thing that changed is adding
getAllTickets
to the dependency array, it makes sense that this is what's causing an infinite loop -- but why?Remember that a hook will re-run when any of its dependencies change. But
getAllTickets
is a function -- how is it changing?To debug this and understand what's going on, you will need to use the browser's native JavaScript debugger to step through the code. Once you add the
debugger;
statements, the browser developer tools should pop open and pause on the line of code. It should look like this:info> Debugger didn't pop up? You may need to open the developer tools manually by pressing F12 (for Chrome, Firefox, and Edge).
Notice how the
useTicketDb
hook is returning an object with thegetAllTickets
property.Go ahead and hit "Resume script execution" (the play button) until you stop within the
TicketList
component. In the right sidebar you can inspect variables, and thegetAllTickets
is a function.If you click Resume again, and again, you'll see that you're always executing the
useTicketDb
hook and returning that object.When objects are not equal
To the untrained eye, it may seem like
getAllTickets
is not changing -- but it is! Every time theuseTicketDb
function executes, it returns a new object instance every time. SincegetAllTickets
is an inline function, it's a new instance every time. Each instance has the same shape but it's not the same object instance. This means that the shallow equality comparison fails and the hook logic says:"Okay,
getAllTickets
has changed. I should re-run my effect!"And then you have an infinitely running hook.
Reusing the same instance
To fix this issue, somehow you need to have
getAllTickets
be the same instance across function executions. One way to accomplish this is by moving the functions outside theuseTicketDb
closure, effectively making them module-scoped functions and declared only once. If you reload the app, you'll see that everything is working again.info> Web Browser tab or page stuck? Infinite hooks can cause your browser tab or frame to hang. You may need to reset the lab environment by closing the Web Browser tab and re-opening it, or re-running
npm run dev
to restart the app, or reloading the whole page (don't worry, your progress is saved!).Now that the reference to
getAllTickets
is stable, the hook doesn't get stuck in a loop.This idea of "stability" is important and will help you fix the next lint warning...
-
Challenge
Memoizing Functions with useCallback
Memoizing Functions with useCallback
The next lint warning suggests the same kind of fix for the
useEffect
in theTicketPager
component (in theTicketList.jsx
module). This time after reloading the app, you may not notice anything immediately. However, try clicking "Next" in the paging component to view more tickets.What?! It resets back to showing the first page!
Try adding a
debugger;
statement again to step through the code. Again, the browser developer tools should pop up with the debugger paused before theuseEffect
hook, like this:If you Resume execution, it hits the breakpoint again and if you keep hitting the play button, it will keep on going.
It turns out, the component is re-rendering infinitely -- you just didn't see any difference because the DOM wasn't changing until you tried paging through the ticket list and it resets to page 1 every update.
The importance of stability
Previously, I said stability is an important concept. Since effect hooks re-run whenever their dependencies change, it means that you always want to ensure the values you pass in the array are stable -- meaning that if they don't materially change, their reference should not change.
Could you fix this by moving the
onPageChange
to be a module-level function? Take a look:const TicketPager = ({ onPageChange, total }) => {
No, the value is being passed as a prop to the component. This means the responsibility for passing a stable value moves up to the parent component.
If you follow the call stack, you'll see:
<TicketPager onPageChange={handlePageChange} total={allTickets.length} />
In the
TicketList
component, andhandlePageChange
is defined as:const handlePageChange = (page) => {
Can this function be moved to the module-scope? Again, no, because it references React component state (
sortedAllTickets
).How can you possibly fix this?
- The
handlePageChange
function is a new instance every timeTicketList
renders. - It calls
setVisibleTickets
which causesTicketList
to re-render (a React state change), - Which then causes
TicketPager
to re-render (becausehandlePageChange
is a new instance), - Which then causes
onPageChange(1)
to be called (because the hook re-runs), - Which then calls
handlePageChange
again... - And on and on until you reach
Infinity
.
There needs to be a way to stabilize
handlePageChange
so that it's not a new function instance every render.Memoization creates stable values
Memoization is a pattern and approach that caches and reuses references based on declared dependencies. This means a value will remain referentially stable every time you ask for it unless a dependency changes. That sounds like hooks, doesn't it? They are built on the same concept.
React provides built-in APIs for memoizing different things -- and one of those is callbacks like
handlePageChange
with theuseCallback
API.It only requires a small change to
handlePageChange
where you wrap the function withuseCallback
and then pass an array of dependencies:import { useCallback } from "react"; const handlePageChange = useCallback((page) => { // ... logic }, [sortedAllTickets]);
useCallback
is a hook, so it works the same way asuseEffect
. If any dependencies change (via shallow equality), the hook returns a new instance of the callback function bound to the new dependency values. Otherwise, it reuses the previous reference and keeps the value stabilized.Why aren't set-state functions added as dependencies?
React automatically creates a stable function for set-state actions, so they are not required to be passed to hook dependency arrays. However, ESLint may sometimes tell you to do so if you are passing a set-state action from a custom hook. It doesn't hurt to do it, but you can also explicitly [disable those lint errors](https://eslint.org/docs/latest/use/configure/rules#disabling-rules).
Why don't you run the
npm run lint
command again and see if there any more obvious issues to address? No warnings and no errors! Your work is done. Right?Observing running code
Not quite. ESLint helps you fix common sources of errors with React apps, including performance issues like stabilizing hook dependencies. It is a "static analysis" tool, meaning that it only scans your source code -- it can't detect issues while the code is running (also called "at runtime").
The industry calls measuring runtime code "observability" and there are some special tools for React that allow you to "see behind the curtain" and inspect how React is rendering your app in quite a lot of detail.
- The
-
Challenge
Optimizing Context and Hook Re-Rendering
Optimizing Context and Hook Re-Rendering
To uncover issues that are harder to detect, you'll want to install the React Developer Tools. These will allow you to inspect and debug how a React app is rendering at runtime.
Starting a profiling session
Once you have the React Developer Tools installed, reload the demo app and there will be two additional tabs in the browser developer tool panel: Components and Profiler.
The Profiler allows you to measure and watch how React renders your application, either from the initial load or during an interaction sequence (like clicking around).
Designing an audit scenario
To conduct a performance audit, you have to design scenarios. Depending on what you want to test, you will need to design different scenarios that follow different paths through the app. This lab will walk through a couple key scenarios to show you how to diagnose different issues.
In the first scenario, you are testing whether displaying a ticket might be causing any performance issues.
You can follow these steps to perform your first profiling audit:
- Click the Profiler tab
- Click the Start Profiling button (🟠).
- Click the same ticket 3-4 times to display it
The resulting profiler output will look something like this:
Interpreting the output
Refer to the annotated numbers in the screenshot for reference
React renders in "commits" which batch updates to the screen. This commit timeline (1) is in the upper-right corner of the profiler, and you can page through it to see the contents of each commit with timing data.
The Flamechart (2) displays the tree of components and the sizes of the bars are the relative rendering time. You can select components to filter the display.
The selected component or commit data is displayed in the details sidebar (3). This contains additional timing info, as well provides reasons why the component rendered (if that option is enabled in the Settings).
Setting expectations
Before performing an audit, it's important to note what you expect to see.
Clicking on a ticket link once should select it and display it, which is what the profiler shows. However, clicking the same ticket multiple times should not result in extra commits -- because the ticket selection hasn't changed.
Debugging hook changes
According to the profiler session, the
TicketContext
is updating each time you click the ticket link and it says:Why did this render? Hook 2 changed
The number refers to the "hook index" which is a 1-based number corresponding to the order of declaration in a component.
In the
TicketContext
, there are two hooks declared:const [isCreating, setIsCreating] = useState(false); const [selectedTicket, setSelectedTicket] = useState(null);
Therefore Hook 2 corresponds to
selectedTicket
state. The profiler is saying thatselectedTicket
is changing every time you click the ticket link.Passing unstable objects
If you inspect
TicketList
component, you will find the following code that sets the selected ticket state:const handleTicketSelected = async (id) => { const ticket = await getTicket(id); setSelectedTicket(ticket); };
The problem is that
getTicket
retrieves an object from IndexedDB which creates a new instance every time its called.ticket
is an unstable object.Controlling Context re-rendering
Since it's unstable, the
TicketContext
will re-render since theselectedTicket
state is updated.When a Context updates, it causes all components that consume it to re-render. Since the
TicketContext
is used in theApp
component, it re-renders the entire application.It's important to control context updates as much as possible if you use them. In this case, it would be best to only call
setSelectedTicket
if the displayed ticket is different than the currently selected ticket.Conditional state updates
There is an overload to set-state actions that accepts a callback that is passed the previous state value:
setSelectedTicket(prevTicket => { // Return the same state as before // which will NOT trigger a state update if (prevTicket?.id === id) { return prevTicket; } // Return new state that will trigger an update return ticket; });
This is a great way to conditionally update state. Memoizing the
handleTicketSelected
callback and updating it to only callsetSelectedTicket
when the ticket ID changes successfully prevents wasted rendering when clicking the same ticket multiple times, as shown in this updated profiling session:In the session I clicked the same ticket several times but there are only two commits instead of four. The second commit is expected and only rendered when changing to a different ticket.
Isolating Context rendering
What else can you see in the profiling flamechart? Colored bars indicate the component updated, and the intensity of the color is the relative render timing.
If you look at the second commit, you'll see that the
App
component and all components underneath it re-render due to "Context changed." Even though the commit time is below 2ms, it's not ideal that the entireApp
component tree is re-rendered whenTicketContext
changes. In a more complex app, this could be a significant performance problem.Sometimes to fix performance issues, you must refactor the application and move hooks closer to where they are needed to isolate their impact.
Piecemeal refactoring
Currently the
App
depends on theTicketContext
but it doesn't need to -- the logic can be moved to other places in the component tree and safely removed fromApp
to isolate the re-rendering.You'll refactor out each dependency on
TicketContext
one-by-one since this a major change.To begin with, you'll remove the dependency on
selectedTicket
by modifying howTicketDisplay
works. Next, you can remove the hook dependencies onaddTicket
,setSelectedTicket
, andsetIsCreating
by moving thehandleTicketCreated
callback logic into theTicketForm
component. After refactoringApp
, running the same scenario steps again produces a much more greyed out flamegraph chart:The grey components are not rendered during the commit, which is exactly what the refactoring was intended to do.
List re-rendering can be expensive
Do you notice how in the second commit, each of the
TicketListItems
are rendering? Why does every list item re-render when the displayed ticket changes? That doesn't seem right.Lists are a common source of performance issues. While displaying 20 tickets doesn't sound like much, each update takes between 0.2 to 1ms. That means re-rendering 1000 tickets in a single commit could lead to a 1 second slowdown depending on the client machine.
Ideally, list items should only re-render when something they're displaying changes.
-
Challenge
Memoizing Components with memo
Memoizing Components with memo
If you select one of the
TicketListItem
components in the flamechart, it says it rendered due to the parent component re-rendering:This seems wasteful -- why should every
TicketListItem
re-render simply because its parent changed?How functional component rendering works
This is by design. Functional components always render anytime their parent component is rendered, even if their props haven't changed.
However, React does allow you to opt-out of this behavior using the
memo
API:import { memo } from "react"; const MemoizedTicketListItem = memo( (props) => /* component code */);
By wrapping a functional component in
memo()
, it will not re-render unless the props are different (and it even allows you to override the prop comparison function).This should be used sparingly, but in some scenarios like list rendering, it can provide some performance gains by avoiding re-rendering list items excessively. Memoizing the component and running the same scenario shows that
TicketListItem
no longer re-renders when changing tickets:While the time savings in this demo lab is minor (~1ms), memoizing components can lead to significant performance gains for expensive list rendering.
-
Challenge
Memoizing Values with useMemo
Memoizing Values with useMemo
The React Developer Tools are an essential debugging tool to identify React rendering issues like excessive rendering. However, the browser's native performance tooling can also help identify potential CPU and memory issues.
Sometimes CPU slowdowns can be impacting React rendering times but the React Developer Tools won't surface these problems.
Running a CPU profile
To run a CPU profile, in the browser developer tools, click the Performance tab (in Chrome or Edge).
Click the Settings gear icon and select CPU throttling to add a 6X slowdown. Throttling your CPU can give you more realistic performance that matches an end-user device and can highlight issues you may be overlooking by using a powerful machine.
You'll run the same scenario, clicking Start profiling, clicking on multiple tickets to display them, and then stopping the profiling session.
You will see a very intimidating looking flamechart like this:
Reviewing source timings
For the purposes of this lab, it will be easier to find issues by browsing the Sources tab. The Sources tab allows you to view JavaScript source maps for your application.
After you run a browser profiling session, your JavaScript sources will have CPU timing data added to the margins next to your code. This makes it easier to identify parts of your code that could be slowing down the app.
In the TicketList.jsx source map, notice how this line that sorts
allTickets
by ID takes up a relatively large amount of CPU cycles (~13ms vs. 0.5ms, or about 26X longer):Why don't you set the debugger to pause before that line of code and see what's potentially going on?
info> If you were following along and turned on CPU throttling, you should now turn it off. Otherwise debugging this code will take much longer than usual. If you reload the app, the debugger should now pop up again (open the browser developer tools, if not).
If you keep pressing Play/Resume, what you should notice is that the breakpoint is getting hit very often -- much more than the 1-2 commits that the React Developer Tools showed.
React updates vs. commits
This is where the React Developer Tools aren't telling you the whole story. A React component may run its update logic multiple times before actually being scheduled for a render commit.
This is so that any state updates can be handled asynchronously and batched together efficiently.
However, it means that logic written in the component function body will execute every update which can be expensive.
Memoizing values
The
sortedTickets
value is based on theallTickets
state. Whenever you need to create a derived value, the calculation is expensive, and you want it to be calculated only when that dependency changes, it is a good candidate for memoization.Unlike the
memo
API which is for components, and theuseCallback
hook which is for functions, React offers theuseMemo
hook for memoizing values:import { useMemo } from "react"; const sortedAllTickets = useMemo( () => /* sort code */, [allTickets]); ``` Enabling CPU throttling and running the CPU profile scenario again produces source timings that show much less total CPU time spent sorting tickets:  ## Reducing CPU usage helps reduce energy usage Incurring ~5ms once with a throttling enabled is orders of magnitude better than incurring 5ms every time you change tickets. It may not seem like much, but in production applications there can be many innocuous lines like this in hundreds of components across the app -- it all adds up! Not only does reducing CPU cycle time help speed up the app, it also uses less energy and power on the user's machine. A win-win for all of us. ## Memoization is not a one-size-fits-all strategy So far you have fixed many of the performance issues through memoization. Does this mean you should always memoize _all_ components, props, and callbacks in React? **No.** The trade-off with memoization is increased memory usage. Since values are cached in-memory, the more objects you memoize (and the more they change), the more your heap size will grow. This is why the best performance fixes involve simplifying, refactoring, or reducing renders through other means besides memoization. Finally, the best performance advice is simply to _measure before changing anything._ Memoization, shared module state, and refactoring can sometimes increase the complexity of the code. Make sure the performance gain is actually worth the added complexity.
-
Challenge
Optimizing Slow Rendering
Optimizing Slow Rendering
Similar to the React Profiler, you will want to perform different interaction scenarios during an audit to uncover CPU slowdowns.
For example, you could measure paging through tickets, a common interaction that support personnel will do.
Taking another CPU profile with a 6X throttle and clicking "Next" a few times to page through the ticket list produces a CPU flamechart with a much more pronounced problem:
Addressing perceived UX problems first
Not only is the problem obvious in the CPU profile, when you clicked the Next or Previous buttons you probably felt the app was slow to respond to your click interaction. This is part of your perceived user experience. According to usability studies, users will perceive anything above 100ms to be delayed. The click events in the profiling output are each in excess of 400ms, or nearly half a second.
When the issue presents itself on the flamegraph like this, you can hover over the chart and select bars to view the function call stack and timing data:
In this case, the problem function is traced to
handleNextPage
in theTicketPager
component. However, the actual code that is slow is in the callbackhandlePageChange
function.You can see the code in the
handlePageChange
function has a very inefficient way of calculating visible tickets, iterating over theallTickets
list and callingindexOf
.Handling expensive calculations
This is a case of an expensive calculation. Big-O notation describes the computational time complexity of an algorithm:
Since the code iterates multiple times over the tickets array, the time complexity is quite bad, leading to a CPU slowdown.
Rather than iterate over the array to calculate indices, this algorithm can be simplified to a single operation using the
Array.slice
method. Re-running the scenario with CPU profiling and throttling should now produce a much more compact flamegraph without the large chunks of processing time:In this profiler session screenshot, each click took about 40ms, a 10X improvement.
Paging through the list should also feel faster and more responsive as it's below the 100ms UX mark.
A snappy app
It appears that your work is done! The support team is reporting a much faster perceived UX now that you've fixed a lot of the wasted rendering and CPU slowdown issues.
-
Challenge
Recap and What's Next
Recap and What's Next
The common thread for many of the performance fixes you implemented was that you were reducing wasted renders.
Your goal is to reduce wasted renders
The principal objective of optimizing React apps is to reduce wasted renders. Wasted renders use up CPU cycles, result in excessive DOM updates, and impact the user experience negatively.
In this lab you reduced wasted renders by:
- Using module-level state to control object lifetime
- Refactoring and isolating hooks to only the components that needed them
- Memoizing callbacks with
useCallback
to stabilize functions - Memoizing values with
useMemo
to reduce expensive computations - Memoizing list items to prevent them from excessively rendering due to their parent changing
- Avoiding setting previous state that triggers unnecessary updates
- Using the browser script profiler and the React Developer Tools to diagnose issues
Learn more
Optimizing React applications is a large topic and there are some advanced APIs that weren't covered.
You can reference the following to learn more and dive deeper into optimization topics:
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.