State Management Overview
Why State Management?
React Native components are driven by state. As application complexity grows, state may be distributed across multiple components, leading to challenges such as:
- State Sharing Difficulty: Passing props between components becomes cumbersome.
- Data Consistency Issues: Multiple components modifying the same state can cause conflicts.
- Debugging Complexity: Tracking the source of state changes is difficult.
State management tools address these issues through centralized or reactive approaches, improving code maintainability and predictability.
Comparison of Four Approaches
| Approach | Complexity | Use Case | Advantages | Disadvantages |
|---|---|---|---|---|
| Redux | High | Large apps, global state | Strong predictability, easy debugging | Boilerplate-heavy |
| MobX | Medium | Medium apps, reactive needs | Simple, minimal code | Less explicit state changes |
| Context API | Low | Small apps, lightweight global state | Native, easy to learn | Performance issues in complex scenarios |
| React Hooks | Low | Local state, simple global state | Flexible, fewer dependencies | Not ideal for complex global state |
Redux: Global State Management
Basic Concepts and Workflow
Redux is a global state management library based on a unidirectional data flow. Core concepts include:
- Store: A single state tree storing all data.
- Action: Plain objects describing state changes.
- Reducer: Pure functions that update state based on actions.
Data Flow
- User triggers an Action.
- Reducer processes the Action and returns a new state.
- Store updates, and components re-render.
Implementing a Simple Redux Application
We’ll create a counter application.
Installation
npm install redux react-redux
Code Implementation
- Create Reducer In
src/redux/reducers.js:
const initialState = {
count: 0,
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
export default counterReducer;
- Create Store In
src/redux/store.js:
import { createStore } from 'redux';
import counterReducer from './reducers';
const store = createStore(counterReducer);
export default store;
- Connect Components In
App.js:
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { Provider, useSelector, useDispatch } from 'react-redux';
import store from './src/redux/store';
const Counter = () => {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<View style={styles.container}>
<Text>Count: {count}</Text>
<Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} />
<Button title="Decrement" onPress={() => dispatch({ type: 'DECREMENT' })} />
</View>
);
};
const App = () => {
return (
<Provider store={store}>
<Counter />
</Provider>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default App;
Analysis
Provider: Injects the Store into the application.useSelector: Retrieves state from the Store.useDispatch: Dispatches actions to update state.
Simplifying with Redux Toolkit
Redux Toolkit reduces boilerplate code with a simpler API.
Installation
npm install @reduxjs/toolkit
Modified Code In src/redux/counterSlice.js:
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: state => { state.count += 1; },
decrement: state => { state.count -= 1; },
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
In src/redux/store.js:
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
const store = configureStore({
reducer: counterReducer,
});
export default store;
In App.js:
import { increment, decrement } from './src/redux/counterSlice';
// In Counter component
<Button title="Increment" onPress={() => dispatch(increment())} />
<Button title="Decrement" onPress={() => dispatch(decrement())} />
Analysis
createSlice: Automatically generates Actions and Reducers.- Immutability: Uses Immer internally, allowing direct state modifications.
MobX: Reactive State Management
Basic Concepts and Workflow
MobX is a reactive state management library that automatically updates the UI via an observer pattern. Key concepts:
- Observable: Trackable state.
- Action: Operations that modify state.
- Reaction: Responses to state changes.
Implementing a MobX Example
Installation
npm install mobx mobx-react
Code Implementation
- Create Store In
src/stores/counterStore.js:
import { makeAutoObservable } from 'mobx';
class CounterStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
}
export default new CounterStore();
- Connect Components In
App.js:
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { observer } from 'mobx-react';
import counterStore from './src/stores/counterStore';
const Counter = observer(() => {
return (
<View style={styles.container}>
<Text>Count: {counterStore.count}</Text>
<Button title="Increment" onPress={() => counterStore.increment()} />
<Button title="Decrement" onPress={() => counterStore.decrement()} />
</View>
);
});
const App = () => <Counter />;
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default App;
Analysis
makeAutoObservable: Automatically makes properties and methods observable.observer: Makes components reactive to state changes.- Simplicity: No need for explicit action dispatching.
Context API: Lightweight Global State
Basic Concepts and Use Cases
The Context API is React’s built-in global state management tool, suitable for small applications or scenarios requiring cross-component data sharing.
Implementing a Context API Example
Code Implementation In src/context/CounterContext.js:
import React, { createContext, useState } from 'react';
export const CounterContext = createContext();
export const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<CounterContext.Provider value={{ count, increment, decrement }}>
{children}
</CounterContext.Provider>
);
};
In App.js:
import React, { useContext } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { CounterContext, CounterProvider } from './src/context/CounterContext';
const Counter = () => {
const { count, increment, decrement } = useContext(CounterContext);
return (
<View style={styles.container}>
<Text>Count: {count}</Text>
<Button title="Increment" onPress={increment} />
<Button title="Decrement" onPress={decrement} />
</View>
);
};
const App = () => {
return (
<CounterProvider>
<Counter />
</CounterProvider>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default App;
Analysis
createContext: Creates a context object.Provider: Provides state and methods.useContext: Consumes context values.
React Hooks: Local State Management
useState and useReducer
useState is ideal for simple state, while useReducer suits complex logic.
Example Code
import React, { useState, useReducer } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT': return { count: state.count + 1 };
case 'DECREMENT': return { count: state.count - 1 };
default: return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const [simpleCount, setSimpleCount] = useState(0);
return (
<View style={styles.container}>
<Text>useReducer Count: {state.count}</Text>
<Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} />
<Button title="Decrement" onPress={() => dispatch({ type: 'DECREMENT' })} />
<Text>useState Count: {simpleCount}</Text>
<Button title="Increment" onPress={() => setSimpleCount(simpleCount + 1)} />
<Button title="Decrement" onPress={() => setSimpleCount(simpleCount - 1)} />
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default App;
Analysis
useState: Simple and direct, ideal for independent state.useReducer: Similar to Redux, suitable for complex state logic.
Global State with useContext
Combine useContext and useReducer for lightweight global state management.
Example Code In src/hooks/useCounter.js:
import { createContext, useContext, useReducer } from 'react';
const CounterContext = createContext();
const counterReducer = (state, action) => {
switch (action.type) {
case 'INCREMENT': return { count: state.count + 1 };
case 'DECREMENT': return { count: state.count - 1 };
default: return state;
}
};
export const CounterProvider = ({ children }) => {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
};
export const useCounter = () => useContext(CounterContext);
In App.js:
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { CounterProvider, useCounter } from './src/hooks/useCounter';
const Counter = () => {
const { state, dispatch } = useCounter();
return (
<View style={styles.container}>
<Text>Count: {state.count}</Text>
<Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} />
<Button title="Decrement" onPress={() => dispatch({ type: 'DECREMENT' })} />
</View>
);
};
const App = () => (
<CounterProvider>
<Counter />
</CounterProvider>
);
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default App;
Analysis
- Global Scope: Shares state via Context.
- Modularity: Encapsulates logic in a custom Hook.
Comprehensive Case Study
Requirement Analysis
We’ll create a task list application that:
- Adds tasks.
- Displays the task count.
- Implements solutions using Redux, MobX, and Hooks.
Implementation with Multiple Approaches
Redux Implementation
// src/redux/taskSlice.js
import { createSlice } from '@reduxjs/toolkit';
const taskSlice = createSlice({
name: 'tasks',
initialState: { tasks: [] },
reducers: {
addTask: (state, action) => { state.tasks.push(action.payload); },
},
});
export const { addTask } = taskSlice.actions;
export default taskSlice.reducer;
// src/redux/store.js
import { configureStore } from '@reduxjs/toolkit';
import taskReducer from './taskSlice';
export default configureStore({ reducer: taskReducer });
// App.js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import { Provider, useSelector, useDispatch } from 'react-redux';
import store from './src/redux/store';
import { addTask } from './src/redux/taskSlice';
const TaskApp = () => {
const [task, setTask] = useState('');
const tasks = useSelector(state => state.tasks);
const dispatch = useDispatch();
return (
<View style={styles.container}>
<Text>Tasks: {tasks.length}</Text>
<TextInput
style={styles.input}
value={task}
onChangeText={setTask}
placeholder="Add a task"
/>
<Button title="Add" onPress={() => { dispatch(addTask(task)); setTask(''); }} />
<FlatList
data={tasks}
renderItem={({ item }) => <Text>{item}</Text>}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
};
const App = () => (
<Provider store={store}>
<TaskApp />
</Provider>
);
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
input: { borderWidth: 1, padding: 10, marginVertical: 10 },
});
export default App;
MobX Implementation
// src/stores/taskStore.js
import { makeAutoObservable } from 'mobx';
class TaskStore {
tasks = [];
constructor() {
makeAutoObservable(this);
}
addTask(task) {
this.tasks.push(task);
}
}
export default new TaskStore();
// App.js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import { observer } from 'mobx-react';
import taskStore from './src/stores/taskStore';
const TaskApp = observer(() => {
const [task, setTask] = useState('');
return (
<View style={styles.container}>
<Text>Tasks: {taskStore.tasks.length}</Text>
<TextInput
style={styles.input}
value={task}
onChangeText={setTask}
placeholder="Add a task"
/>
<Button title="Add" onPress={() => { taskStore.addTask(task); setTask(''); }} />
<FlatList
data={taskStore.tasks}
renderItem={({ item }) => <Text>{item}</Text>}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
});
const App = () => <TaskApp />;
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
input: { borderWidth: 1, padding: 10, marginVertical: 10 },
});
export default App;
Hooks Implementation
// src/hooks/useTasks.js
import { createContext, useContext, useReducer } from 'react';
const TaskContext = createContext();
const taskReducer = (state, action) => {
switch (action.type) {
case 'ADD_TASK': return { tasks: [...state.tasks, action.payload] };
default: return state;
}
};
export const TaskProvider = ({ children }) => {
const [state, dispatch] = useReducer(taskReducer, { tasks: [] });
return (
<TaskContext.Provider value={{ state, dispatch }}>
{children}
</TaskContext.Provider>
);
};
export const useTasks = () => useContext(TaskContext);
// App.js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import { TaskProvider, useTasks } from './src/hooks/useTasks';
const TaskApp = () => {
const [task, setTask] = useState('');
const { state, dispatch } = useTasks();
return (
<View style={styles.container}>
<Text>Tasks: {state.tasks.length}</Text>
<TextInput
style={styles.input}
value={task}
onChangeText={setTask}
placeholder="Add a task"
/>
<Button title="Add" onPress={() => { dispatch({ type: 'ADD_TASK', payload: task }); setTask(''); }} />
<FlatList
data={state.tasks}
renderItem={({ item }) => <Text>{item}</Text>}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
};
const App = () => (
<TaskProvider>
<TaskApp />
</TaskProvider>
);
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
input: { borderWidth: 1, padding: 10, marginVertical: 10 },
});
export default App;
Analysis
- Redux: Clear structure, ideal for scalability.
- MobX: Concise code, reactive updates.
- Hooks: Lightweight and flexible, suitable for small to medium applications.



