This guide shows how to use Redux and API in your screens.
import React, { useEffect } from 'react';
import { View, FlatList, ActivityIndicator, Text } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from '../store';
import { fetchProducts } from '../store/slices/productSlice';
export const ProductListScreen = () => {
const dispatch = useDispatch<AppDispatch>();
const { products, loading, error } = useSelector(
(state: RootState) => state.products
);
useEffect(() => {
// Fetch products when component mounts
dispatch(fetchProducts({ page: 1, limit: 20 }));
}, [dispatch]);
if (loading) return <ActivityIndicator />;
if (error) return <Text>Error: {error}</Text>;
return (
<FlatList
data={products}
renderItem={({ item }) => <Text>{item.name}</Text>}
keyExtractor={item => item.id}
/>
);
};import React, { useState } from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from '../store';
import { addToCart } from '../store/slices/cartSlice';
import Toast from 'react-native-toast-message';
export const AddToCartButton = ({ productId }) => {
const dispatch = useDispatch<AppDispatch>();
const { loading } = useSelector((state: RootState) => state.cart);
const [quantity, setQuantity] = useState(1);
const handleAddToCart = async () => {
try {
const result = await dispatch(
addToCart({ productId, quantity })
).unwrap();
Toast.show({
type: 'success',
text1: 'Added to cart',
});
} catch (error) {
Toast.show({
type: 'error',
text1: 'Failed to add to cart',
});
}
};
return (
<TouchableOpacity onPress={handleAddToCart} disabled={loading}>
<Text>{loading ? 'Adding...' : 'Add to Cart'}</Text>
</TouchableOpacity>
);
};import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from '../store';
import { loginUser, clearError } from '../store/slices/authSlice';
import { ErrorMessage } from '../components/common/Button';
export const LoginForm = ({ navigation }) => {
const dispatch = useDispatch<AppDispatch>();
const { loading, error } = useSelector((state: RootState) => state.auth);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async () => {
dispatch(clearError()); // Clear previous errors
try {
const result = await dispatch(
loginUser({ email, password })
).unwrap();
// Navigation happens automatically based on role
// See RootNavigator.tsx
} catch (err) {
// Error is already in Redux state
// Display it with: {error}
}
};
return (
<View>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<ErrorMessage message={error} />
<TouchableOpacity onPress={handleLogin} disabled={loading}>
<Text>{loading ? 'Signing in...' : 'Sign In'}</Text>
</TouchableOpacity>
</View>
);
};import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, ScrollView } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from '../store';
import { createProduct } from '../store/slices/productSlice';
import Toast from 'react-native-toast-message';
export const CreateProductScreen = ({ navigation }) => {
const dispatch = useDispatch<AppDispatch>();
const { loading, error } = useSelector((state: RootState) => state.products);
const [formData, setFormData] = useState({
name: '',
description: '',
price: '',
stock: '',
category: '',
});
const handleChange = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async () => {
try {
const productData = {
...formData,
price: parseFloat(formData.price),
stock: parseInt(formData.stock),
};
const result = await dispatch(createProduct(productData)).unwrap();
Toast.show({
type: 'success',
text1: 'Product created successfully',
});
navigation.goBack();
} catch (err) {
Toast.show({
type: 'error',
text1: 'Failed to create product',
});
}
};
return (
<ScrollView>
<TextInput
placeholder="Product Name"
value={formData.name}
onChangeText={val => handleChange('name', val)}
/>
<TextInput
placeholder="Description"
value={formData.description}
onChangeText={val => handleChange('description', val)}
multiline
/>
<TextInput
placeholder="Price"
value={formData.price}
onChangeText={val => handleChange('price', val)}
keyboardType="decimal-pad"
/>
<TextInput
placeholder="Stock"
value={formData.stock}
onChangeText={val => handleChange('stock', val)}
keyboardType="number-pad"
/>
<TouchableOpacity onPress={handleSubmit} disabled={loading}>
<Text>{loading ? 'Creating...' : 'Create Product'}</Text>
</TouchableOpacity>
</ScrollView>
);
};Sometimes you want to call API directly without Redux:
import api from '../services/api';
const fetchUserOrders = async () => {
try {
const response = await api.getOrders(1, 10);
if (response.success) {
console.log('Orders:', response.data);
} else {
console.error('Error:', response.error);
}
} catch (error) {
console.error('Failed to fetch orders:', error);
}
};
// Call it
fetchUserOrders();Component mounts
β
dispatch(fetchProducts())
β
Redux thunk calls API
β
API returns data
β
Reducer updates state
β
Component re-renders with new data
import { useSelector } from 'react-redux';
import { RootState } from '../store';
const MyComponent = () => {
// Get auth state
const { user, token, isAuthenticated } = useSelector(
(state: RootState) => state.auth
);
// Get products state
const { products, loading } = useSelector(
(state: RootState) => state.products
);
// Get cart state
const { cart } = useSelector((state: RootState) => state.cart);
return (
// Use state in JSX
);
};import { useDispatch } from 'react-redux';
import { AppDispatch } from '../store';
import { loginUser } from '../store/slices/authSlice';
const MyComponent = () => {
const dispatch = useDispatch<AppDispatch>();
const handleClick = () => {
dispatch(loginUser({ email: 'test@test.com', password: '123456' }));
};
return <button onClick={handleClick}>Login</button>;
};try {
const result = await dispatch(loginUser(credentials)).unwrap();
// Success - result contains data
console.log('Login successful:', result);
} catch (error) {
// Error handling
console.error('Login failed:', error);
}const { error, loading } = useSelector((state: RootState) => state.auth);
if (error) {
return <ErrorMessage message={error} />;
}
if (loading) {
return <ActivityIndicator />;
}import Toast from 'react-native-toast-message';
try {
await dispatch(createProduct(data)).unwrap();
Toast.show({
type: 'success',
text1: 'Success!',
text2: 'Product created',
});
} catch (error) {
Toast.show({
type: 'error',
text1: 'Error',
text2: error.message || 'Something went wrong',
});
}The API client automatically:
- Reads token from AsyncStorage
- Adds it to Authorization header
- Handles token expiration (401)
- Clears token on logout
// In API service (already implemented)
const token = await AsyncStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}You don't need to manually add tokens - it's automatic!
useEffect(() => {
dispatch(fetchProducts({ page: 1 }));
}, [dispatch]);const handleLoadMore = () => {
dispatch(fetchProducts({
page: pagination.page + 1,
limit: 20
}));
};const handleRefresh = () => {
dispatch(fetchProducts({ page: 1, limit: 20 }));
};const { data, loading, error } = useSelector(...);
if (error) return <ErrorView />;
if (loading) return <LoadingSpinner />;
if (!data || data.length === 0) return <EmptyView />;
return <DataList data={data} />;| Use Redux | Use Local State |
|---|---|
| Auth data | Form inputs |
| Global products | UI toggles |
| User profile | Modal visibility |
| Cart items | Loading during form submit |
| Orders | Dropdown selection |
- Always use AppDispatch type -
const dispatch = useDispatch<AppDispatch>() - Use .unwrap() - Simplifies error handling
- Clear errors - Call
clearError()before new operations - Show loading states - Disable buttons, show spinners
- Display errors - Use Toast or ErrorMessage components
- Handle edge cases - Empty lists, failed requests, no internet
Need more examples? Check the screens in src/screens/ for real-world usage!