Mehedi Hassan Piash | Senior Software Engineer | Android | iOS | KMP | Ktor | Jetpack Compose | React-Native.

February 16, 2025

Centralized Loading and Error Handling in React Native with Middleware and Zustand

February 16, 2025 Posted by Piash , , , , No comments

 Managing loading states and handling errors efficiently is crucial for creating a smooth user experience in React Native applications. In this blog, we will explore how to centralize loading state management using Redux Toolkit Query (RTK Query) and middleware, while leveraging Zustand for error handling.

Centralized Loading Management with RTK Query

1. Centralized Loading Management with RTK Query

RTK Query simplifies API state management by handling caching, invalidation, and background fetching. We can centralize the loading state by selecting the active API requests using a global selector.

Defining a Global Loading Selector

import { createSelector } from '@reduxjs/toolkit';
import { movieApi } from '../../src/redux/query/RTKQuery.ts';
const selectGlobalLoading = createSelector(
(state: any) => state[movieApi.reducerPath],
(apiState) => {
const isFetchingQueries = Object.values(apiState.queries).some(
(query: any) => query?.status === 'pending'
);
const isFetchingMutations = Object.values(apiState.mutations).some(
(mutation: any) => mutation?.status === 'pending'
);
return isFetchingQueries || isFetchingMutations;
}
);
export { selectGlobalLoading };

This selector scans all active API queries and mutations, checking if any are still in a “pending” state.

Creating a Global Loading Spinner

To display a loading indicator whenever an API call is in progress, we use the useSelector hook.

import React from 'react';
import { ActivityIndicator, View } from 'react-native';
import styles from './LoadingSpinner.style.ts';
import { useSelector } from 'react-redux';
import { selectGlobalLoading } from '../../utils/Common.ts';
import { colors } from '../../constant/Colors.ts';
const LoadingSpinner = () => {
const isLoading = useSelector(selectGlobalLoading);
if (!isLoading) return null;
return (
<View style={styles.container}>
<ActivityIndicator size={'large'} color={colors.primaryColor} />
</View>
);
};
export default LoadingSpinner;

This component listens for loading state updates and displays an overlay with an ActivityIndicator when an API request is in progress.

Adding the Loading Spinner to the App

We integrate the LoadingSpinner component into the main application layout:

<SafeAreaProvider>
<Navigation />
<NetworkConnection />
<LoadingSpinner />
</SafeAreaProvider>

This ensures that the loading indicator is always accessible at the root level of the app.

2. Centralized Error Handling Using Middleware and Zustand

Instead of manually handling API errors in each component, we can use Redux middleware to capture errors globally and manage them using Zustand.

Defining Zustand Store for Error Handling

Zustand is a lightweight state management library that provides a simple API for managing global state.

import { create } from 'zustand';
interface ErrorState {
visible: boolean;
message: string;
showError: (message: string) => void;
hideError: () => void;
}
export const useApiErrorStore = create<ErrorState>((set) => ({
visible: false,
message: '',
showError: (message: string) => set({ visible: true, message }),
hideError: () => set({ visible: false, message: '' }),
}));

This store exposes methods to show and hide error messages globally.

Creating an RTK Middleware for Error Handling

Middleware allows us to intercept Redux actions and handle errors in a centralized way.

import { isRejectedWithValue, Middleware } from '@reduxjs/toolkit';
import { useApiErrorStore } from '../zustand-store/ApiErrorStore.ts';
interface ErrorResponse {
status: number;
data: {
message?: string;
error?: string;
};
}
export const rtkQueryErrorMiddleware: Middleware = () => (next) => (action) => {
if (isRejectedWithValue(action)) {
const error = action.payload as ErrorResponse;
const errorMessage =
error.data?.message ||
error.data?.error ||
'Something went wrong. Please try again.';
if (errorMessage !== 'Invalid token')
useApiErrorStore.getState().showError(errorMessage);
}
return next(action);
};

This middleware listens for rejected API requests and automatically updates the Zustand store with the error message.

Integrating Middleware in Redux Store Configuration

We add the error-handling middleware to the Redux store:

import { configureStore } from '@reduxjs/toolkit';
import logger from 'redux-logger';
import { movieApi } from './query/RTKQuery.ts';
import { setupListeners } from '@reduxjs/toolkit/query';
import { rtkQueryErrorMiddleware } from './RtkQueryErrorMiddleware.ts';
const configureAppStore = () => {
const store = configureStore({
reducer: {
[movieApi.reducerPath]: movieApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat([
logger,
rtkQueryErrorMiddleware,
movieApi.middleware,
]),
devTools: process.env.NODE_ENV === 'development',
});
setupListeners(store.dispatch);
return store;
};
export default configureAppStore;

This ensures that API errors are caught and processed before they reach UI components.

Conclusion

By implementing a centralized loading state using RTK Query and Redux, combined with middleware-driven error handling and Zustand, we have built a robust foundation for managing API state in a React Native app. This approach improves maintainability and ensures a smoother user experience by automatically handling loading indicators and displaying error messages globally.

GitHub: https://github.com/piashcse/react-native-movie
Medium: https://piashcse.medium.com/centralized-loading-and-error-handling-in-react-native-with-middleware-and-zustand-683efd02b792

0 comments:

Post a Comment