Speed Up β‘ your React app with these Techniques
Emmanuel Alozie
Posted on March 12, 2023
When building my startup MVPs I'm usually in a constant dilema of using a faster framework, but the community keeps me tied with React and I love speed so here are the techniques I use to speed up my React apps.
1. Replace your useState for useRef in form elements
Observe this piece of code
import {useState} from "react"
export default Function App(){
const [email, setEmail] = useState<string>("")
const [password, setPassword] = useState<string>("")
function handleSubmit(e){
e.preventDefault()
console.log(email, password)
}
return (
<form onSubmit={handleSubmit}>
<input type="text" id="email" value={email} onChange={(e) => setEmail(e.target.value)}/>
<input type="text" id="password" value={password} onChange={(e) => setPassword(e.target.value)}/>
<button type="button">Submit Me</button>
</form>
)
}
You're probably familiar with this and wondering what's wrong with it, but using useState
can cause unecessary re-renders, and the useState values aren't used anywhere else than in the handleSubmit()
function
So we don't really care what the value of these variables are as they change. So we can make this more efficient using useRef()
import {useRef} from "react"
export default Function App(){
const email = useRef<string>("")
const password = useRef<string>("")
function handleSubmit(e){
e.preventDefault()
console.log(email.current.value, password.current.value)
}
return (
<form onSubmit={handleSubmit}>
<input type="text" id="email" ref={email}/>
<input type="text" id="password" ref={password}/>
<button type="button">Submit Me</button>
</form>
)
}
Once you've made this change, you'll notice that as you enter the fields for the variables, your component will not re-render everytime the state is updated and the functionality remains the same.
2. Windowing
A common requirement for websites is to display a list of data that may scroll up or down, but rendering a large set of data will significantly degrade you application's performance and this could freeze or crash the browser on slower devices
A great technique is virtualization or windowing which is a technique to render only the items that are visible to the user
For example a 1000 todos need to be loaded, we can choose to render a small amount of items that fits on that window, until you scroll to see the rest of the items
For better user experience you can implement scrolling indicators and loaders and the good news is that you don't have to implement them yourself there are libraries that are designed to help you with this namely;
react-window
react virtualized
But if you are feeling daring, you can also implement your own solutions, but i would not recommend this.
3. React Suspense and Lazy Loading
Lazy loading is an efficient way to render and speed up your application, the idea is to load a component only when it's needed.
You don't need to install any extra dependencies for this, React comes with the lazy API right out of the box so you can render a dynamic import as a regular component
So Instead of importing your app like this
import About from "./About"
You would import it with lazy loading like
import { lazy } from "react"
const About = lazy(() => import('./About'))
To cut down the risk of performance bottlenecks if your app has alot of pages.
A lazy component is typically rendered inside a <Suspense></Suspense>
component which allows you to add a fallback UI, while React is fetching you lazy loaded component
import { lazy, Suspense } from "react"
const About = lazy(() => import('./About'))
export default function App(){
return(
<Suspense fallback={<div>Loading... </div>} >
<About/>
</Suspense>
)
}
4. Memoize Store
Frequent iteraction with a store to retrieve the same data is redundant and unecessary. Memoization is a technique that is often applied to the application but it can also be applied to state management systems like
- Redux
- Recoil
- MobX
For example the reselect
library exports { createSelector }
import { createSelector } from "reselect"
That would generate a memoized selector functions, these functions will remember the last values from when they were first invoked and doesn't recalculate if objects are the same.
The re-reselect
library takes this futher is deeper memoization and caching is required. It solves the problem of the regular reselect
library having only a cache limit of one, and switching between different arguments causes cache invalidation
They both offer great solutions but you have to choose which best soothes you application. To recap
-
reselect
- single cache -
re-reselect
- multi cache
5. Avoid Unnecessary Re-renders
When a component re-renders react will also re-render it's children component by default and on larger applications this can slow it down and make the user experience and seo bad, nobody wants a laggy website
On functional components we can avoid this by using useMemo()
import React from "react"
const ListItem = React.memo({item}) => {
return {
<ul>
<li>{item}</li>
</ul>
}
}
This ensures the component is only re-rendered when it's props change.
6. Keep State Local
Keeping the state local to the component will always be faster than using a state management library. My recommendation is use the state management libraries sparing and alternate to using hooks and keeping the state local.
Twitter knows this all too well and this opimization reduced their over head by 50% (86ms)
7. Use web workers
Javascript is a single-threaded programming language meaning you can do only one thing at once.
Web workers make it possible to run a process in web applications background thread separate from the single execution thread, meaning our application and achieve parallel compute without being blocked or slowed down by another function.
Thanks for reading π, follow β for more
Posted on March 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.