Zustand State Management
Learn to use Zustand, a lightweight state management library that provides a simple and intuitive API for managing state in React Native applications. This is a foundational concept in cross-platform mobile development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React Native experience. Take your time with each section and practice the examples
Zustand Overview
Zustand is a small, fast, and scalable state management solution. It has a minimal API and doesn't require providers or complex setup, making it perfect for React Native applications.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Zustand Features
- Minimal boilerplate - No providers or complex setup
- TypeScript support - Built-in TypeScript support — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Devtools integration - Redux DevTools support — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Middleware support - Custom middleware capabilities
- Persistence - Built-in persistence options — a critical concept in cross-platform mobile development that you will use frequently in real projects
Installing Zustand
Install Zustand: `npm install zustand`. For React Native, you might also want to install `zustand/middleware` for additional features.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Zustand Example
import React from 'react';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { View, Text, TouchableOpacity, StyleSheet, TextInput, FlatList } from 'react-native';
// Counter Store
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
// Todo Store with Persistence
const useTodoStore = create(
persist(
(set, get) => ({
todos: [],
filter: 'all',
addTodo: (text) => set((state) => ({
todos: [...state.todos, {
id: Date.now(),
text,
completed: false,
createdAt: Date.now(),
}]
})),
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
})),
deleteTodo: (id) => set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
setFilter: (filter) => set({ filter }),
clearCompleted: () => set((state) => ({
todos: state.todos.filter(todo => !todo.completed)
})),
getFilteredTodos: () => {
const { todos, filter } = get();
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
},
}),
{
name: 'todo-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Counter Component
const Counter = () => {
const { count, increment, decrement, reset } = useCounterStore();
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Counter: {count}</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={decrement}>
<Text style={styles.buttonText}>-</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={increment}>
<Text style={styles.buttonText}>+</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.resetButton]} onPress={reset}>
<Text style={styles.buttonText}>Reset</Text>
</TouchableOpacity>
</View>
</View>
);
};
// Todo Component
const TodoApp = () => {
const {
todos,
filter,
addTodo,
toggleTodo,
deleteTodo,
setFilter,
clearCompleted,
getFilteredTodos,
} = useTodoStore();
const [newTodoText, setNewTodoText] = React.useState('');
const handleAddTodo = () => {
if (newTodoText.trim()) {
addTodo(newTodoText.trim());
setNewTodoText('');
}
};
const filteredTodos = getFilteredTodos();
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Todo App (Persistent)</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
value={newTodoText}
onChangeText={setNewTodoText}
placeholder="Add a new todo..."
onSubmitEditing={handleAddTodo}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAddTodo}>
<Text style={styles.addButtonText}>Add</Text>
</TouchableOpacity>
</View>
<View style={styles.filterContainer}>
<TouchableOpacity
style={[styles.filterButton, filter === 'all' && styles.activeFilter]}
onPress={() => setFilter('all')}
>
<Text style={styles.filterText}>All ({todos.length})</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.filterButton, filter === 'active' && styles.activeFilter]}
onPress={() => setFilter('active')}
>
<Text style={styles.filterText}>Active ({todos.filter(t => !t.completed).length})</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.filterButton, filter === 'completed' && styles.activeFilter]}
onPress={() => setFilter('completed')}
>
<Text style={styles.filterText}>Completed ({todos.filter(t => t.completed).length})</Text>
</TouchableOpacity>
</View>
{todos.some(todo => todo.completed) && (
<TouchableOpacity style={styles.clearButton} onPress={clearCompleted}>
<Text style={styles.clearButtonText}>Clear Completed</Text>
</TouchableOpacity>
)}
<FlatList
data={filteredTodos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.todoItem}>
<TouchableOpacity
style={styles.todoText}
onPress={() => toggleTodo(item.id)}
>
<Text style={[
styles.todoTextContent,
item.completed && styles.completedTodo
]}>
{item.text}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => deleteTodo(item.id)}
>
<Text style={styles.deleteButtonText}>×</Text>
</TouchableOpacity>
</View>
)}
style={styles.todoList}
ListEmptyComponent={
<Text style={styles.emptyText}>
{filter === 'all' ? 'No todos yet' :
filter === 'active' ? 'No active todos' :
'No completed todos'}
</Text>
}
/>
</View>
);
};
// Main App
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Zustand State Management</Text>
<Counter />
<TodoApp />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
section: {
marginBottom: 30,
backgroundColor: 'white',
padding: 20,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 15,
color: '#333',
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'center',
},
button: {
backgroundColor: '#2196F3',
padding: 10,
marginHorizontal: 5,
borderRadius: 5,
minWidth: 50,
alignItems: 'center',
},
resetButton: {
backgroundColor: '#f44336',
},
buttonText: {
color: 'white',
fontWeight: 'bold',
},
inputContainer: {
flexDirection: 'row',
marginBottom: 15,
},
textInput: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 5,
padding: 10,
marginRight: 10,
fontSize: 16,
},
addButton: {
backgroundColor: '#4CAF50',
padding: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
},
addButtonText: {
color: 'white',
fontWeight: 'bold',
},
filterContainer: {
flexDirection: 'row',
marginBottom: 15,
},
filterButton: {
padding: 8,
marginRight: 10,
borderRadius: 5,
backgroundColor: '#e0e0e0',
},
activeFilter: {
backgroundColor: '#2196F3',
},
filterText: {
fontSize: 12,
color: '#333',
},
clearButton: {
backgroundColor: '#ff9800',
padding: 8,
borderRadius: 5,
alignItems: 'center',
marginBottom: 15,
},
clearButtonText: {
color: 'white',
fontSize: 12,
fontWeight: 'bold',
},
todoList: {
maxHeight: 200,
},
todoItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
todoText: {
flex: 1,
},
todoTextContent: {
fontSize: 16,
color: '#333',
},
completedTodo: {
textDecorationLine: 'line-through',
color: '#999',
},
deleteButton: {
backgroundColor: '#f44336',
width: 30,
height: 30,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
deleteButtonText: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
emptyText: {
textAlign: 'center',
color: '#999',
fontStyle: 'italic',
marginTop: 20,
},
});
export default App;