Zustand Simplified: Effortless State Management for Favorites in React Native
State management is a cornerstone of any React Native application. While libraries like Redux are popular, sometimes we need something lightweight yet powerful. Enter Zustand, a minimalist state management library that’s perfect for handling state in React Native. In this article, we’ll explore how to use Zustand to manage a favorites list for Movies and TV series in a React Native app.
Why Use Zustand in React Native?
Zustand is simple, scalable, and performant. Here’s why it’s a great choice for React Native:
1. Minimal Boilerplate: Create stores without cumbersome setup.
2. Middleware Support: Includes built-in middleware for features like persistence.
3. Lightweight: Tiny bundle size and fast performance.
4. Reactivity: Automatically triggers re-renders when state changes.
5. Integration with MMKV Storage: Perfect for persisting data efficiently in React Native.
Setting Up Zustand in React Native
First, ensure you have the required packages installed:
npm install zustand
npm install react-native-mmkv zustand/middleware
Here’s how to set up a Zustand store for managing favorite Movies and TV series:
FavoriteStore.ts
import { create } from 'zustand';
import { MovieDetail } from '../types/MovieDetail.ts';
import { persist, createJSONStorage } from 'zustand/middleware';
import { TvSeriesDetail } from '../types/TvSeriesDetail.ts';
import { zustandMMKVStorage } from './mmkv.ts';
interface FavoriteMoviesTvSeriesStore {
favoriteMovies: MovieDetail[];
toggleFavoriteMovie: (movie: MovieDetail) => void;
isFavoriteMovie: (movieId: number) => boolean;
clearFavoriteMovies: () => void;
favoriteTvSeries: TvSeriesDetail[];
toggleFavoriteTvSeries: (movie: TvSeriesDetail) => void;
isFavoriteTvSeries: (tvSeriesId: number) => boolean;
clearFavoriteTvSeries: () => void;
}
export const useFavoriteStore = create<FavoriteMoviesTvSeriesStore>()(
persist(
(set, get) => ({
favoriteMovies: [],
toggleFavoriteMovie: (movie: MovieDetail) => {
set((state: FavoriteMoviesTvSeriesStore) => {
const isFav = state.favoriteMovies.some(
(favMovie: MovieDetail) => favMovie.id === movie.id
);
return {
favoriteMovies: isFav
? state.favoriteMovies.filter(
(favMovie: MovieDetail) => favMovie.id !== movie.id
) // Remove from favorites
: [...state.favoriteMovies, movie], // Add to favorites
};
});
},
isFavoriteMovie: (movieId: number) =>
get().favoriteMovies.some((movie: MovieDetail) => movie.id === movieId),
clearFavoriteMovies: () => set({ favoriteMovies: [] }),
favoriteTvSeries: [],
toggleFavoriteTvSeries: (tvSeries: TvSeriesDetail) => {
set((state: FavoriteMoviesTvSeriesStore) => {
const isFav = state.favoriteTvSeries.some(
(favTvSeries: TvSeriesDetail) => favTvSeries.id === tvSeries.id
);
return {
favoriteTvSeries: isFav
? state.favoriteTvSeries.filter(
(favTvSeries: TvSeriesDetail) =>
favTvSeries.id !== tvSeries.id
) // Remove from favorites
: [...state.favoriteTvSeries, tvSeries], // Add to favorites
};
});
},
isFavoriteTvSeries: (tvSeriesId: number) =>
get().favoriteTvSeries.some(
(tvSeries: TvSeriesDetail) => tvSeries.id === tvSeriesId
),
clearFavoriteTvSeries: () => set({ favoriteTvSeries: [] }),
}),
{
name: '@favorite',
storage: createJSONStorage(() => zustandMMKVStorage),
}
)
);
MMKV Integration: The zustandMMKVStorage integrates Zustand with react-native-mmkv, a high-performance storage solution. Here’s a simple MMKV wrapper:
MMKV Wrapper (mmkv.ts)
import { MMKV } from 'react-native-mmkv';
const mmkv = new MMKV();
export const zustandMMKVStorage = {
getItem: (name: string) => {
const value = mmkv.getString(name);
return value ? JSON.parse(value) : null;
},
setItem: (name: string, value: any) => {
mmkv.set(name, JSON.stringify(value));
},
removeItem: (name: string) => {
mmkv.delete(name);
},
};
Features Explained
- Add/Remove Favorite Movies or TV Series: Use toggleFavoriteMovie and toggleFavoriteTvSeries to add or remove items based on their presence in the list.
2. Check if an Item is a Favorite: Use isFavoriteMovie or isFavoriteTvSeries to check whether a specific item is in the favorites list.
3. Clear Favorites: Use clearFavoriteMovies and clearFavoriteTvSeries to reset the lists.
4. Persistence with MMKV: The store is persisted using MMKV for fast, efficient local storage.
Using the Store in Components
import React, { useState } from 'react';
import { FlatList, View } from 'react-native';
import styles from './FavoriteMovie.style.ts';
import { useFavoriteStore } from '../../../local-store/FavoriteStore.ts';
import FavoriteComponent from '../../../components/favorite/FavoriteComponent.tsx';
import { MovieDetail } from '../../../types/MovieDetail.ts';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParam } from '../../../types/navigation/NavigationTypes.ts';
import ConfirmationAlert from '../../../components/alert-dialog/ConfirmationAlert.tsx';
import { confirmationAlert } from '../../../constant/Dictionary.ts';
type FavoriteMovieNavigationProp = NavigationProp<
RootStackParam,
'MovieDetail'
>;
const FavoriteMovie = () => {
const navigation = useNavigation<FavoriteMovieNavigationProp>();
const { favoriteMovies, toggleFavoriteMovie } = useFavoriteStore();
const [visible, setVisible] = useState(false);
const [movieToRemove, setMovieToRemove] = useState<MovieDetail | null>(null);
const showDialog = (movie: MovieDetail) => {
setMovieToRemove(movie);
setVisible(true);
};
const hideDialog = () => {
setVisible(false);
setMovieToRemove(null);
};
const confirmRemoveFavorite = () => {
if (movieToRemove) {
toggleFavoriteMovie(movieToRemove);
}
hideDialog();
};
const favorite = ({ item }: { item: MovieDetail }) => (
<FavoriteComponent
title={item.title}
poster_path={item.poster_path}
onPress={() => navigation.navigate('MovieDetail', { movieId: item.id })}
onRemove={() => showDialog(item)}
/>
);
return (
<View style={styles.mainView}>
<FlatList
style={styles.flatListStyle}
data={favoriteMovies}
renderItem={favorite}
keyExtractor={(item) => item.id.toString()} // Ensure a unique key for each item
/>
<ConfirmationAlert
visible={visible}
title={confirmationAlert.title}
message={confirmationAlert.message}
onConfirm={confirmRemoveFavorite}
onCancel={hideDialog}
/>
</View>
);
};
export default FavoriteMovie;
GitHub link: https://github.com/piashcse/react-native-movie
Blog: https://piashcse.blogspot.com/2024/11/zustand-simplified-effortless-state.html