Skip to content

Latest commit

Β 

History

History
426 lines (354 loc) Β· 10.2 KB

File metadata and controls

426 lines (354 loc) Β· 10.2 KB

Redux & API Usage Examples

This guide shows how to use Redux and API in your screens.

πŸ“‘ Using Redux + API Together

Example 1: Fetch Products and Display

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}
    />
  );
};

Example 2: Add to Cart

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>
  );
};

Example 3: User Login

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>
  );
};

Example 4: Create Product (Vendor)

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>
  );
};

πŸ“‘ Direct API Usage (Without Redux)

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();

πŸ”„ Redux State Flow

Component mounts
    ↓
dispatch(fetchProducts())
    ↓
Redux thunk calls API
    ↓
API returns data
    ↓
Reducer updates state
    ↓
Component re-renders with new data

πŸ“Š Accessing Redux State

In Functional Components

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
  );
};

⚑ Dispatching Actions

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>;
};

❌ Error Handling Patterns

Pattern 1: Try/Catch with Unwrap

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);
}

Pattern 2: Using Redux State Error

const { error, loading } = useSelector((state: RootState) => state.auth);

if (error) {
  return <ErrorMessage message={error} />;
}

if (loading) {
  return <ActivityIndicator />;
}

Pattern 3: Toast Notifications

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',
  });
}

πŸ” Auth Token Usage

The API client automatically:

  1. Reads token from AsyncStorage
  2. Adds it to Authorization header
  3. Handles token expiration (401)
  4. 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!

πŸ“ Common Patterns

Pattern: Fetch Data on Mount

useEffect(() => {
  dispatch(fetchProducts({ page: 1 }));
}, [dispatch]);

Pattern: Pagination

const handleLoadMore = () => {
  dispatch(fetchProducts({
    page: pagination.page + 1,
    limit: 20
  }));
};

Pattern: Refresh Data

const handleRefresh = () => {
  dispatch(fetchProducts({ page: 1, limit: 20 }));
};

Pattern: Conditional Rendering

const { data, loading, error } = useSelector(...);

if (error) return <ErrorView />;
if (loading) return <LoadingSpinner />;
if (!data || data.length === 0) return <EmptyView />;
return <DataList data={data} />;

🎯 When to Use Redux vs State

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

πŸ’‘ Best Practices

  1. Always use AppDispatch type - const dispatch = useDispatch<AppDispatch>()
  2. Use .unwrap() - Simplifies error handling
  3. Clear errors - Call clearError() before new operations
  4. Show loading states - Disable buttons, show spinners
  5. Display errors - Use Toast or ErrorMessage components
  6. Handle edge cases - Empty lists, failed requests, no internet

Need more examples? Check the screens in src/screens/ for real-world usage!