From REST API to Reactive UI – Using Zustand
When building dashboards, two common problems often come up – handling global state across different components and fetching data from a REST API in a clean way. Using heavy state management tools like Redux can be too much for smaller or medium projects.
That’s where Zustand helps. It’s a lightweight and easy-to-use state management library for React. Unlike Redux, it doesn’t need extra setup like actions or reducers. Zustand use simple hooks to create and manage state, making it perfect for dashboards where many components share the same data.
Why Zustand?
Zustand offer a modern approach to global state management with a hook-based API. Here’s a breakdown:
Pros :
- Minimal boilerplate – no reducers, action types, or providers required.
- Direct state access – any component can read and update state instantly.
- Async-friendly – easily integrate with APIs using async actions.
- Performance – components only re-render when the part of the state they subscribe to changes.
- Scalable – supports multiple stores for modular state management.
Potential Pitfalls :
- No built-in devtools – requires additional middleware for advanced debugging.
- Less familiar for teams used to Redux – might require a learning curve.
- Not ideal for extremely complex state flows – very large applications may still benefit from Redux or other solutions with stricter patterns.
When to Use / When Not
Use Zustand if :
- You have medium-to-small React apps or dashboards.
- You want simple, readable code with minimal boilerplate.
- You need fast and reactive state management for UI updates.
Avoid Zustand if :
- Your application is massive and highly complex with intricate state interactions.
- Your team heavily relies on Redux DevTools and strict action/reducer patterns.
How Zustand Reactivity Works (Step-by-Step)
Component mounts :
- When a component uses useStore(selector), it subscribes to the Zustand store.
- Zustand keeps track of which part of the state (the “slice”) that component is using.
Zustand registers the subscriber :
- It stores the selector function and the previous selected value (what the component is currently showing).
State updates (store.setState is called) :
- Some action or function changes the state inside the store.
Zustand checks if that slice changed :
- Zustand runs the selector again on the new state.
- It compare the new value with the previous one.
If the slice changed – Re-render :
- Zustand triggers a React re-render only for components that use that changed piece of state.
If the slice didn’t change – No re-render :
- Components that use unchanged state don’t re-render – this keeps your app fast.
Project Setup with Vite, React, TypeScript, and MUI
We’ll use Vite for a fast and modern React setup, along with TypeScript for type safety and Material UI (MUI) for styling components.
Tech Stack and What We’ll Build :
Before diving into the code, let’s clarify the tools and libraries we’ll use to build this dashboard:
- React with TypeScript – for building a type-safe, scalable front-end.
- Vite – a fast development build tool to bootstrap our React project.
- Zustand – to manage global state easily across the dashboard.
- Axios – to fetch data from a REST API.
- Material UI (MUI) – for quick, modern, and responsive UI components and styling.
What We’re Building :
A dynamic weather dashboard showing live update in a clean, modern interface. It pull data from a REST API to display temperature, wind speed, wind direction, and the last updated time.
Search for any city to see its weather instantly. The dashboard updates automatically whenever new data comes in.
Built with React, TypeScript, and Vite, using Zustand for simple state management. The design has a dark, glassy look with soft glowing highlights. Modular panels make it easy to add charts or maps later.
Step 1 : Create a New Vite Project
Run the following command :
npm create vite@latest zustand-dashboard
- You’ll be prompted to select a framework. Choose :
React
- Then select :
TypeScript
- Navigate into the project folder :
cd zustand-dashboard
Step 2 : Install Dependencies
Install the necessary packages for state management, API calls, and styling :
npm install axios zustand @mui/material @emotion/react @emotion/styled
Explanation of packages :
- axios – for REST API requests.
- zustand – for state management.
- @mui/material, @emotion/react, @emotion/styled – for Material UI components and styling.
REST API for Dashboard
For demonstration, we’ll use a ready-made weather API (or you can replace it with your own backend).
Endpoint :
GET https://api.langfuse.com/v1/metrics
Creating the Zustand Store
Create a store to manage weather state :
// src/zustand-store/weatherStore.ts
import { create } from "zustand";
import axios from "axios";
interface WeatherData {
city: string;
temperature: number;
windSpeed: number;
windDirection: number;
weatherCode: number;
time: string;
}
interface WeatherState {
weather: WeatherData | null;
loading: boolean;
error: string | null;
fetchWeather: (city: string) => Promise;
clearWeather: () => void;
}
export const useWeatherStore = create((set) => ({
weather: null,
loading: false,
error: null,
fetchWeather: async (city: string) => {
if (!city) {
set({ error: "City is required", loading: false });
return;
}
set({ loading: true, error: null });
try {
// Step 1 Get coordinates using Open-Meteo Geocoding API
const geoRes = await axios.get(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
city
)}&count=1`
);
if (!geoRes.data.results || geoRes.data.results.length === 0) {
set({ error: "City not found", loading: false });
return;
}
const { latitude, longitude, name } = geoRes.data.results[0];
// Step 2 Fetch live weather data
const weatherRes = await axios.get(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`
);
const data = weatherRes.data.current_weather;
set({
weather: {
city: name,
temperature: data.temperature,
windSpeed: data.windspeed,
windDirection: data.winddirection,
weatherCode: data.weathercode,
time: data.time,
},
loading: false,
});
} catch (err: any) {
set({
error: err.message || "Failed to fetch weather",
loading: false,
});
}
},
clearWeather: () => set({ weather: null, error: null, loading: false }),
}));
How the Zustand Store Works in weatherStore.ts
- Creating the Store
- useWeatherStore is created using create<WeatherState> from Zustand.
- It hold state values (weather, loading, error) and methods (fetchWeather, clearWeather) in one central place.
- State Values
- weather: Stores the fetched weather data or null if not fetched.
- loading: Boolean flag indicating if a request is in progress.
- error: Holds any error messages during API calls.
- fetchWeather(city : string)
- Sets loading: true and clears previous errors.
- Step 1 : Call the Open-Meteo Geocoding API to get coordinates for the city.
- Step 2 : Call the Open-Meteo Weather API using the coordinates to get current weather data.
- Updates weather in the store with API results.
- Handles errors by setting error and resetting loading.
- clearWeather()
- Resets the store: weather = null, loading = false, error = null.
- Useful to clear the dashboard when the user clears the input.
- Automatic Re-rendering
- Components subscribing to useWeatherStore automatically update whenever state changes (e.g., after fetchWeather or clearWeather).
- Centralized Functionality
- Both data and logic for fetching/clearing weather are encapsulated in one store, avoiding prop drilling.
Building the Search Bar to Fetch Weather Data :
// src/components/SearchBar.tsx
//Necessary Imports
const SearchBar: React.FC = () => {
const [city, setCity] = useState("");
const { fetchWeather, clearWeather } = useWeatherStore();
const handleSearch = async () => {
if (!city.trim()) return;
await fetchWeather(city.trim());
};
const handleClear = () => {
setCity("");
clearWeather();
};
return (
setCity(e.target.value)}
/>
);
};
export default SearchBar;
SearchBar Functionality :
- Local state (city) : Stores user input.
- Search : Calls fetchWeather(city) from the store.
- Clear : Resets input and calls clearWeather() in the store.
- Delegates logic : All API fetching and error handling is handled by the Zustand store.
Building the Weather Dashboard Component :
// src/components/WeatherDashboard.tsx
//Necessary Imports
const WeatherDashboard: React.FC = () => {
const { weather, loading, error } = useWeatherStore();
return (
Weather Dashboard
{loading && (
)}
{error && (
{error}
)}
{weather && !loading && (
{weather.city}
🌡 {weather.temperature}°C
💨 Wind: {weather.windSpeed} m/s
🧭 Direction: {weather.windDirection}°
⏰ Updated: {new Date(weather.time).toLocaleString()}
)}
);
};
export default WeatherDashboard;
WeatherDashboard Functionality :
- Uses store state : Reads weather, loading, and error from useWeatherStore.
- Displays SearchBar : Lets users input a city and trigger fetchWeather.
- Loading state : Shows a spinner while data is being fetched.
- Error state : Displays any errors from the store.
- Weather data display: Renders current weather details when available.
- Reactive updates : Automatically re-renders when the store state changes.
Setting Up the App Component :
// src/App.tsx
import React from "react";
import WeatherDashboard from "./components/WeatherDashboard";
const App: React.FC = () => {
return (
);
};
export default App;
Renders the WeatherDashboard component as the main app view, serving as the entry point for the weather dashboard.
Running the Application :
npm start
Visit http://localhost:3000 to see the dashboard in action. All metrics are fetched from the REST API, and Zustand manages the global state seamlessly.
Live Preview: View Dynamic Dashboard on CodeSandbox
CONCLUSION
This guide explains how to build a weather dashboard using React, TypeScript, and Zustand. The app fetches live weather data and shows it in a clear and simple way. Zustand helps manage state without extra code. Components only update when needed, which keeps the app fast. Following this guide gives hands-on experience with building responsive dashboards and shows how Zustand makes state management easier.

