- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- 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.
Lab 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 devagain 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/componentsholds different components that are on the pagesrc/hooksholds a custom hook to work with IndexedDB, the web-based storage engine and the idb packagesrc/contextshold a shared context to share state in the appApp.jsxis the main component that renders the page- The
TicketFormcomponent encapsulates the form fields and handling form submission - The
TicketDisplaycomponent handles displaying ticket information to the user - The
TicketListcomponent handles displaying the list of tickets in the database
Additionally, the file tree has been organized into modules within the
srcfolder 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.jsxfile.Running effects on mount
The console message originates in a Promise
thenmethod, 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
openDBis being called twice -- and you'd be right. But why?The
useEffecthas 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
useTicketDbhook is being used in two places:src/App.jsxsrc/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
openDBtwice, 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
useStatefor thedbinstance. Can it go any higher? You could store thedbreference 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
dbinstance.openDBreturns a Promise, which can be stored in module state. This allows each hook instance to "wait for" the Promise to resolve. TheopenDBPromise will set adbvariable 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
initializedstate 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
dbinstances 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 theseedDbfunction 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-depsrule: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-depsviolationsThe first warning suggests fixing the issue by adding
getAllTicketsto the hook dependency array in theTicketListcomponent.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
getAllTicketsto 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
getAllTicketsis 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
useTicketDbhook is returning an object with thegetAllTicketsproperty.Go ahead and hit "Resume script execution" (the play button) until you stop within the
TicketListcomponent. In the right sidebar you can inspect variables, and thegetAllTicketsis a function.If you click Resume again, and again, you'll see that you're always executing the
useTicketDbhook and returning that object.When objects are not equal
To the untrained eye, it may seem like
getAllTicketsis not changing -- but it is! Every time theuseTicketDbfunction executes, it returns a new object instance every time. SincegetAllTicketsis 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,
getAllTicketshas 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
getAllTicketsbe the same instance across function executions. One way to accomplish this is by moving the functions outside theuseTicketDbclosure, 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 devto restart the app, or reloading the whole page (don't worry, your progress is saved!).Now that the reference to
getAllTicketsis 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
useEffectin theTicketPagercomponent (in theTicketList.jsxmodule). 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 theuseEffecthook, 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
onPageChangeto 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
TicketListcomponent, andhandlePageChangeis 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
handlePageChangefunction is a new instance every timeTicketListrenders. - It calls
setVisibleTicketswhich causesTicketListto re-render (a React state change), - Which then causes
TicketPagerto re-render (becausehandlePageChangeis a new instance), - Which then causes
onPageChange(1)to be called (because the hook re-runs), - Which then calls
handlePageChangeagain... - And on and on until you reach
Infinity.
There needs to be a way to stabilize
handlePageChangeso 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
handlePageChangewith theuseCallbackAPI.It only requires a small change to
handlePageChangewhere you wrap the function withuseCallbackand then pass an array of dependencies:import { useCallback } from "react"; const handlePageChange = useCallback((page) => { // ... logic }, [sortedAllTickets]);useCallbackis 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.After memoizing the callback, you can now page through the tickets as before.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 lintcommand 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
TicketContextis 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
selectedTicketstate. The profiler is saying thatselectedTicketis changing every time you click the ticket link.Passing unstable objects
If you inspect
TicketListcomponent, 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
getTicketretrieves an object from IndexedDB which creates a new instance every time its called.ticketis an unstable object.Controlling Context re-rendering
Since it's unstable, the
TicketContextwill re-render since theselectedTicketstate is updated.When a Context updates, it causes all components that consume it to re-render. Since the
TicketContextis used in theAppcomponent, 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
setSelectedTicketif 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
handleTicketSelectedcallback and updating it to only callsetSelectedTicketwhen 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
Appcomponent 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 entireAppcomponent tree is re-rendered whenTicketContextchanges. 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
Appdepends on theTicketContextbut it doesn't need to -- the logic can be moved to other places in the component tree and safely removed fromAppto isolate the re-rendering.You'll refactor out each dependency on
TicketContextone-by-one since this a major change.To begin with, you'll remove the dependency on
selectedTicketby modifying howTicketDisplayworks. Next, you can remove the hook dependencies onaddTicket,setSelectedTicket, andsetIsCreatingby moving thehandleTicketCreatedcallback logic into theTicketFormcomponent. 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
TicketListItemsare 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
TicketListItemcomponents in the flamechart, it says it rendered due to the parent component re-rendering:
This seems wasteful -- why should every
TicketListItemre-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
memoAPI: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
TicketListItemno 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
allTicketsby 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
sortedTicketsvalue is based on theallTicketsstate. 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
memoAPI which is for components, and theuseCallbackhook which is for functions, React offers theuseMemohook 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
handleNextPagein theTicketPagercomponent. However, the actual code that is slow is in the callbackhandlePageChangefunction.You can see the code in the
handlePageChangefunction has a very inefficient way of calculating visible tickets, iterating over theallTicketslist 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.slicemethod. 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
useCallbackto stabilize functions - Memoizing values with
useMemoto 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:
About the author
Real skill practice before real-world application
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.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.