React App Optimization 101


“Yes, it works, but can it be faster than that?”
This was a request that I received as I was working on a real-time data monitoring platform built in React. The request was perfectly reasonable (i.e., make the platform handle user interactions at an optimized speed). It seemed quite straightforward, only that it wasn’t.
Quick backstory: I was using a mapping library called Mapbox (a feature-rich mapping library behind popular apps like Strava) to load hundreds of data assets onto a map every 3 seconds. A user would then interact with the assets through actions like filtering. My application was taking a whopping 10 seconds to load and even more to reflect user interactions. Yet, according to Google's Core Web Vitals, the acceptable load time for a web page is 2 seconds, 3 at maximum.So what exactly was going on and how could I resolve the bottleneck?
In this blog post, I’ll share the real reason behind this performance issue. I’ll discuss the options I explored and show a demo of how I optimized my application for better performance.
Let’s dig in !
The Problem
Initially, I thought the reason behind the lag was my application design.I was calling an api,storing the response in local state and the using the data in state to create features on the map.So started to brainstorm:
What if I used global state or a state management library like Redux or Zustand?
What if I didn’t use state at all and instead used global variables to manipulate data as I needed it to behave?
What if I used useRef on state to prevent unnecessary re-renders?
What if I used UseReducer instead as the state logic could be harbouring complex logic underneath?
What if used useMemo to memoize the results of the functions powering user interactions.
What if I pieced together all of the above to form some mega optimized app?
I tried different variations of the solutions above with each attempt leaving me a bit more frustrated. Omitting state completely led to brittle spaghetti code while the other alternatives did not lead to an improvement in application performance.I finally arrived at my Eureka moment.Turns out I was lacking a method to query fresh data according to the set interval while making data available for user manipulation before the next update.This finally led me to Tanstack Query. As its homepage rightly states, it’s a powerful way to load real-time data and auto-managed manipulations on said data.
Show me the Code !
I will give a quick overview of an example that shows how I used Tanstack Query to seamlessly cache and refetch data for the real-time application.
- First set up React and Mapbox. You can view how to get started with Mapbox on their official docs here
npm create vite@latest
cd my-map-app
npm install
npm install mapbox-gl
I mocked an api by deploying a node backend service which returns coordinates of cell towers from a geoJSON file. You can find the code here
- Set up the map page on a React Component
import React from 'react'
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { useState, useRef, useEffect } from 'react'
import { useQuery } from "@tanstack/react-query";
function MapBody() {
const [locationData, setLocationData] = useState();
const INITIAL_CENTER = [36.82598043111716, -1.2990087795643603];
const INITIAL_ZOOM = 10.5;
const mapRef = useRef();
const mapContainerRef = useRef();
useEffect(() => {
if (mapRef.current) return; // Prevent re-initializing the map
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
mapRef.current = new mapboxgl.Map({
container: mapContainerRef.current,
zoom: INITIAL_ZOOM,
center: INITIAL_CENTER,
});
//adding some navigation settings
mapRef.current.dragRotate.disable();
mapRef.current.touchZoomRotate.disableRotation();
if (mapRef.current) {
mapRef.current.addControl(
new mapboxgl.NavigationControl({ showCompass: true })
);
}
}, []);
return (
<>
<div>
<div
id="map-container"
ref={mapContainerRef}
className="w-screen h-screen absolute inset-0 z-10 bg-gray-200 position-fixed padding-0 margin-0 overflow-hidden"
/>
</div>
</>
)
}
export default MapBody
- Add the function that fetches data from the api call and cache the response via the TanStack method useQuery.
async function getLocations() {
try {
const response = await fetch("https://map-my-wheels.onrender.com/api/random_towers", {
method: "GET",
headers: {
'Content-Type': 'application/json'
}
})
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const towerJsonData = await response.json();
console.log("response object:", towerJsonData);
return towerJsonData;
} catch (err) {
console.error("Error fetching data", err);
}
}
//caching and refreshing data every 10 seconds
const { data: queryTowerData, isLoading, error, refetch } = useQuery({
queryKey: ["towerLocation"],
queryFn: getLocations,
refetchInterval: 3000,
staleTime: 3000,
});
// Update locationData when queryTowerData changes
useEffect(() => {
if (queryTowerData) {
setLocationData(queryTowerData);
// Update the map source if it exists
if (mapRef.current && mapRef.current.getSource("cell-towers")) {
mapRef.current.getSource("cell-towers").setData(queryTowerData);
}
}
console.log("DATA STATE", queryTowerData);
}, [queryTowerData]);
I added more code to create the various features on the map and pop ups showing information about the cell towers.The final product looked like this:
You can interact with the demo here and view the full solution in this Github repository
While my demo shows only 6 cell towers, it can scale to handle hundreds of cell-towers all broadcasting at interval of every 3 seconds or less.The same approach can be repurposed for viewing data transmissions in different real time streaming/tracking usecases from common delivery apps to even flight tracking systems.
Final Words
Building functional React apps is one thing; optimizing a data-heavy app for real-time monitoring is another challenge entirely. Thankfully, React provides powerful hooks and tools to help — but without understanding the root causes of performance bottlenecks, these tools alone won't be enough. I'm grateful for the opportunity to dive deep into different strategies and ultimately find a solution that worked.
Wishing you all the best as you tackle your own front-end performance challenges!
Subscribe to my newsletter
Read articles from Stephanie Mukami directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
