react-native-sop-docs/05-state-management-navigation/api-integration.md

537 lines
12 KiB
Markdown

# API Integration
## RTK Query Setup
```typescript
// src/services/api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import Config from 'react-native-config';
import { RootState } from '@redux/store';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: Config.API_BASE_URL,
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
headers.set('Content-Type', 'application/json');
return headers;
},
}),
tagTypes: ['User', 'NGO', 'Donation', 'Request'],
endpoints: () => ({}),
});
```
## Auth API
```typescript
// src/services/authAPI.ts
import { api } from './api';
interface LoginRequest {
email: string;
password: string;
}
interface LoginResponse {
user: {
id: string;
email: string;
firstName: string;
lastName: string;
};
token: string;
refreshToken: string;
}
interface RegisterRequest {
firstName: string;
lastName: string;
email: string;
password: string;
phone: string;
}
export const authAPI = api.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation<LoginResponse, LoginRequest>({
query: (credentials) => ({
url: '/auth/login',
method: 'POST',
body: credentials,
}),
}),
register: builder.mutation<LoginResponse, RegisterRequest>({
query: (userData) => ({
url: '/auth/register',
method: 'POST',
body: userData,
}),
}),
refreshToken: builder.mutation<{ token: string }, { refreshToken: string }>(
{
query: ({ refreshToken }) => ({
url: '/auth/refresh',
method: 'POST',
body: { refreshToken },
}),
},
),
logout: builder.mutation<void, void>({
query: () => ({
url: '/auth/logout',
method: 'POST',
}),
}),
forgotPassword: builder.mutation<void, { email: string }>({
query: ({ email }) => ({
url: '/auth/forgot-password',
method: 'POST',
body: { email },
}),
}),
verifyOTP: builder.mutation<void, { email: string; otp: string }>({
query: ({ email, otp }) => ({
url: '/auth/verify-otp',
method: 'POST',
body: { email, otp },
}),
}),
}),
});
export const {
useLoginMutation,
useRegisterMutation,
useRefreshTokenMutation,
useLogoutMutation,
useForgotPasswordMutation,
useVerifyOTPMutation,
} = authAPI;
```
## User API
```typescript
// src/services/userAPI.ts
import { api } from './api';
interface User {
id: string;
email: string;
firstName: string;
lastName: string;
phone?: string;
avatar?: string;
isNGO: boolean;
ngoDetails?: NGODetails;
}
interface NGODetails {
ngoName: string;
registrationNumber: string;
address: string;
description: string;
causes: string[];
}
interface UpdateProfileRequest {
firstName?: string;
lastName?: string;
phone?: string;
avatar?: string;
}
export const userAPI = api.injectEndpoints({
endpoints: (builder) => ({
getProfile: builder.query<User, void>({
query: () => '/user/profile',
providesTags: ['User'],
}),
updateProfile: builder.mutation<User, UpdateProfileRequest>({
query: (updates) => ({
url: '/user/profile',
method: 'PUT',
body: updates,
}),
invalidatesTags: ['User'],
}),
uploadAvatar: builder.mutation<{ avatarUrl: string }, FormData>({
query: (formData) => ({
url: '/user/avatar',
method: 'POST',
body: formData,
}),
invalidatesTags: ['User'],
}),
deleteAccount: builder.mutation<void, void>({
query: () => ({
url: '/user/account',
method: 'DELETE',
}),
}),
}),
});
export const {
useGetProfileQuery,
useUpdateProfileMutation,
useUploadAvatarMutation,
useDeleteAccountMutation,
} = userAPI;
```
## NGO API
```typescript
// src/services/ngoAPI.ts
import { api } from './api';
interface NGO {
id: string;
ngoName: string;
description: string;
registrationNumber: string;
address: string;
causes: string[];
coverImage?: string;
isVerified: boolean;
totalDonations: number;
activeRequests: number;
}
interface CreateNGORequest {
ngoName: string;
description: string;
registrationNumber: string;
address: string;
causes: string[];
documents: string[];
}
export const ngoAPI = api.injectEndpoints({
endpoints: (builder) => ({
getNGOList: builder.query<
NGO[],
{ page?: number; limit?: number; search?: string }
>({
query: ({ page = 1, limit = 10, search }) => ({
url: '/ngo',
params: { page, limit, search },
}),
providesTags: ['NGO'],
}),
getNGOById: builder.query<NGO, string>({
query: (id) => `/ngo/${id}`,
providesTags: ['NGO'],
}),
createNGO: builder.mutation<NGO, CreateNGORequest>({
query: (ngoData) => ({
url: '/ngo',
method: 'POST',
body: ngoData,
}),
invalidatesTags: ['NGO', 'User'],
}),
updateNGO: builder.mutation<
NGO,
{ id: string; updates: Partial<CreateNGORequest> }
>({
query: ({ id, updates }) => ({
url: `/ngo/${id}`,
method: 'PUT',
body: updates,
}),
invalidatesTags: ['NGO'],
}),
getFavoriteNGOs: builder.query<NGO[], void>({
query: () => '/ngo/favorites',
providesTags: ['NGO'],
}),
toggleFavoriteNGO: builder.mutation<void, string>({
query: (ngoId) => ({
url: `/ngo/${ngoId}/favorite`,
method: 'POST',
}),
invalidatesTags: ['NGO'],
}),
}),
});
export const {
useGetNGOListQuery,
useGetNGOByIdQuery,
useCreateNGOMutation,
useUpdateNGOMutation,
useGetFavoriteNGOsQuery,
useToggleFavoriteNGOMutation,
} = ngoAPI;
```
## Request API
```typescript
// src/services/requestAPI.ts
import { api } from './api';
interface FundraisingRequest {
id: string;
title: string;
description: string;
category: string;
targetAmount: number;
currentAmount: number;
images: string[];
location: {
latitude: number;
longitude: number;
address: string;
};
status: 'active' | 'completed' | 'cancelled';
createdBy: string;
ngoId?: string;
isUrgent: boolean;
endDate: string;
}
interface CreateRequestData {
title: string;
description: string;
category: string;
targetAmount: number;
images: string[];
location: {
latitude: number;
longitude: number;
address: string;
};
isUrgent: boolean;
endDate: string;
}
export const requestAPI = api.injectEndpoints({
endpoints: (builder) => ({
getRequests: builder.query<
FundraisingRequest[],
{
page?: number;
limit?: number;
category?: string;
location?: string;
urgent?: boolean;
}
>({
query: (params) => ({
url: '/requests',
params,
}),
providesTags: ['Request'],
}),
getRequestById: builder.query<FundraisingRequest, string>({
query: (id) => `/requests/${id}`,
providesTags: ['Request'],
}),
createRequest: builder.mutation<FundraisingRequest, CreateRequestData>({
query: (requestData) => ({
url: '/requests',
method: 'POST',
body: requestData,
}),
invalidatesTags: ['Request'],
}),
updateRequest: builder.mutation<
FundraisingRequest,
{
id: string;
updates: Partial<CreateRequestData>;
}
>({
query: ({ id, updates }) => ({
url: `/requests/${id}`,
method: 'PUT',
body: updates,
}),
invalidatesTags: ['Request'],
}),
deleteRequest: builder.mutation<void, string>({
query: (id) => ({
url: `/requests/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['Request'],
}),
getMyRequests: builder.query<FundraisingRequest[], void>({
query: () => '/requests/my-requests',
providesTags: ['Request'],
}),
}),
});
export const {
useGetRequestsQuery,
useGetRequestByIdQuery,
useCreateRequestMutation,
useUpdateRequestMutation,
useDeleteRequestMutation,
useGetMyRequestsQuery,
} = requestAPI;
```
## Error Handling
```typescript
// src/utils/errorHandler.ts
import { SerializedError } from '@reduxjs/toolkit';
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
export interface APIError {
message: string;
status?: number;
code?: string;
}
export const handleAPIError = (
error: FetchBaseQueryError | SerializedError,
): APIError => {
if ('status' in error) {
// FetchBaseQueryError
const status = error.status;
const data = error.data as any;
switch (status) {
case 400:
return {
message: data?.message || 'Bad request',
status: 400,
code: 'BAD_REQUEST',
};
case 401:
return {
message: 'Authentication required',
status: 401,
code: 'UNAUTHORIZED',
};
case 403:
return {
message: 'Access denied',
status: 403,
code: 'FORBIDDEN',
};
case 404:
return {
message: 'Resource not found',
status: 404,
code: 'NOT_FOUND',
};
case 500:
return {
message: 'Server error. Please try again later.',
status: 500,
code: 'SERVER_ERROR',
};
default:
return {
message: data?.message || 'An unexpected error occurred',
status: typeof status === 'number' ? status : undefined,
code: 'UNKNOWN_ERROR',
};
}
} else {
// SerializedError
return {
message: error.message || 'Network error',
code: 'NETWORK_ERROR',
};
}
};
```
## API Hooks
```typescript
// src/hooks/useAPI.ts
import { useState, useCallback } from 'react';
import { handleAPIError, APIError } from '@utils/errorHandler';
interface UseAPIState<T> {
data: T | null;
loading: boolean;
error: APIError | null;
}
export const useAPI = <T>() => {
const [state, setState] = useState<UseAPIState<T>>({
data: null,
loading: false,
error: null,
});
const execute = useCallback(async (apiCall: () => Promise<T>) => {
setState((prev) => ({ ...prev, loading: true, error: null }));
try {
const data = await apiCall();
setState({ data, loading: false, error: null });
return data;
} catch (error: any) {
const apiError = handleAPIError(error);
setState((prev) => ({ ...prev, loading: false, error: apiError }));
throw apiError;
}
}, []);
const reset = useCallback(() => {
setState({ data: null, loading: false, error: null });
}, []);
return {
...state,
execute,
reset,
};
};
```
## Store Integration
```typescript
// src/redux/store/index.ts (updated)
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { combineReducers } from '@reduxjs/toolkit';
import { api } from '@services/api';
import authSlice from '../slices/authSlice';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth'],
};
const rootReducer = combineReducers({
auth: authSlice,
[api.reducerPath]: api.reducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
},
}).concat(api.middleware),
});
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
```