# SWD Axios Query

[![npm version](https://img.shields.io/npm/v/swd-axios-query.svg)](https://www.npmjs.com/package/swd-axios-query)
[![license](https://img.shields.io/npm/l/swd-axios-query.svg)](LICENSE)

A lightweight library that simplifies making API requests in React applications. Combines the power of Axios and React Query for seamless API interactions, caching, and state updates.

---

## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Full Example](#full-example)
- [Core API Reference](#core-api-reference)
- [Optional & Advanced Features](#optional--advanced-features)
  - [Axios Interceptors](#axios-requestresponse-interceptors)
  - [Global Loading/Error State Context](#global-loadingerror-state-context-optional)
- [Contribution & Feedback](#contribution--feedback)

---

## Features

| Feature                                 | Description                                                      |
|-----------------------------------------|------------------------------------------------------------------|
| Easy-to-use hooks                       | For GET, POST, PUT, DELETE requests                              |
| Global Axios/React Query config         | Set base URL, headers, and query options                         |
| Query provider                          | Manages React Query client context                               |
| Custom headers/base URL                 | Add/override headers and base URL globally                       |
| React Query Devtools support            | Enable devtools for debugging                                    |
| **NEW:** Axios interceptors             | Add/remove request/response interceptors globally                |
| **NEW:** Global loading/error context   | Track all API loading/error states with a single provider/hook   |

---

## Installation

```bash
npm install swd-axios-query
# or
yarn add swd-axios-query
```

---

## Quick Start

### 1. Configure the Package
```js
import { configureSWDQuery } from 'swd-axios-query';

configureSWDQuery({
  baseURL: 'https://api.example.com',
  headers: { Authorization: 'Bearer your_token_here' },
  defaultOptions: { retry: 1, refetchOnMount: true },
});
```

### 2. Wrap Your App
```js
import { SWDQueryProvider } from 'swd-axios-query';

<SWDQueryProvider enableDevTools={true}>
  <App />
</SWDQueryProvider>
```

### 3. Use Hooks in Components
```js
import { useGet } from 'swd-axios-query';
import { useEffect } from 'react';

function Todos() {
  const { data, error, isLoading, isSuccess, isError } = useGet(['todos'], '/todos');
  
  // Handle success/error states using useEffect (TanStack Query v5 pattern)
  useEffect(() => {
    if (isSuccess) {
      console.log('Data loaded successfully:', data);
    }
  }, [isSuccess, data]);

  useEffect(() => {
    if (isError) {
      console.error('Error loading data:', error);
    }
  }, [isError, error]);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <ul>{data?.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>;
}
```

---

## Full Example
```js
import React from 'react';
import ReactDOM from 'react-dom';
import {
  SWDQueryProvider,
  SWDGlobalStatusProvider,
  configureSWDQuery,
  addRequestInterceptor,
  addResponseInterceptor,
  useGlobalStatus,
  useGet
} from 'swd-axios-query';

// Configure once at app startup
configureSWDQuery({ baseURL: 'https://api.example.com' });

// Optional: Add interceptors
addRequestInterceptor(config => {
  config.headers.Authorization = 'Bearer ' + localStorage.getItem('token');
  return config;
});
addResponseInterceptor(
  response => response,
  error => {
    if (error.response?.status === 401) {
      // Handle unauthorized globally
    }
    return Promise.reject(error);
  }
);

function GlobalSpinner() {
  const { isLoading } = useGlobalStatus();
  return isLoading ? <div className="spinner">Loading...</div> : null;
}

function Todos() {
  const { data, isLoading, error, isSuccess, isError } = useGet(['todos'], '/todos');
  
  // Handle success/error states using useEffect (TanStack Query v5 pattern)
  useEffect(() => {
    if (isSuccess) {
      console.log('Todos loaded successfully:', data);
    }
  }, [isSuccess, data]);

  useEffect(() => {
    if (isError) {
      console.error('Error loading todos:', error);
    }
  }, [isError, error]);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <ul>{data?.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>;
}

ReactDOM.render(
  <SWDQueryProvider>
    <SWDGlobalStatusProvider>
      <GlobalSpinner />
      <Todos />
    </SWDGlobalStatusProvider>
  </SWDQueryProvider>,
  document.getElementById('root')
);
```

---

## Mutation Examples

### usePost - Creating Data
```js
import { usePost } from 'swd-axios-query';
import { useEffect } from 'react';

function CreateUser() {
  const { mutate, isLoading, error, isSuccess, data } = usePost(
    ['users'], // Query key for cache invalidation
    '/api/users', // API endpoint
    {
      onSuccess: (data) => {
        console.log('User created successfully:', data);
        // Optional: Show success message
      },
      onError: (error) => {
        console.error('Failed to create user:', error);
        // Optional: Show error message
      }
    }
  );

  // Handle success/error states using useEffect (TanStack Query v5 pattern)
  useEffect(() => {
    if (isSuccess) {
      console.log('User created:', data);
      // Optional: Redirect or show success message
    }
  }, [isSuccess, data]);

  useEffect(() => {
    if (error) {
      console.error('Creation failed:', error);
      // Optional: Show error message
    }
  }, [error]);

  const handleSubmit = (formData) => {
    // Direct data passing - no need to wrap in { data: ... }
    mutate({
      name: formData.name,
      email: formData.email,
      age: formData.age
    });
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const formData = new FormData(e.target);
      handleSubmit({
        name: formData.get('name'),
        email: formData.get('email'),
        age: parseInt(formData.get('age'))
      });
    }}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <input name="age" type="number" placeholder="Age" required />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Creating...' : 'Create User'}
      </button>
      {error && <p style={{color: 'red'}}>Error: {error.message}</p>}
    </form>
  );
}
```

### usePut - Updating Data
```js
import { usePut } from 'swd-axios-query';
import { useEffect } from 'react';

function EditUser({ userId, initialData }) {
  const { mutate, isLoading, error, isSuccess, data } = usePut(
    ['users', userId], // Query key for cache invalidation
    `/api/users/${userId}`, // API endpoint with ID
    {
      onSuccess: (data) => {
        console.log('User updated successfully:', data);
      }
    }
  );

  // Handle success/error states
  useEffect(() => {
    if (isSuccess) {
      console.log('User updated:', data);
      // Optional: Show success message or redirect
    }
  }, [isSuccess, data]);

  const handleUpdate = (updatedData) => {
    // Direct data passing
    mutate({
      ...initialData,
      ...updatedData,
      updatedAt: new Date().toISOString()
    });
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const formData = new FormData(e.target);
      handleUpdate({
        name: formData.get('name'),
        email: formData.get('email'),
        age: parseInt(formData.get('age'))
      });
    }}>
      <input 
        name="name" 
        defaultValue={initialData.name} 
        placeholder="Name" 
        required 
      />
      <input 
        name="email" 
        type="email" 
        defaultValue={initialData.email} 
        placeholder="Email" 
        required 
      />
      <input 
        name="age" 
        type="number" 
        defaultValue={initialData.age} 
        placeholder="Age" 
        required 
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Updating...' : 'Update User'}
      </button>
      {error && <p style={{color: 'red'}}>Error: {error.message}</p>}
    </form>
  );
}
```

### useDelete - Deleting Data
```js
import { useDelete } from 'swd-axios-query';
import { useEffect } from 'react';

function DeleteUser({ userId, onDeleted }) {
  const { mutate, isLoading, error, isSuccess } = useDelete(
    ['users', userId], // Query key for cache invalidation
    `/api/users/${userId}`, // API endpoint with ID
    {
      onSuccess: () => {
        console.log('User deleted successfully');
        // Optional: Call callback or show success message
        if (onDeleted) onDeleted();
      }
    }
  );

  // Handle success/error states
  useEffect(() => {
    if (isSuccess) {
      console.log('User deleted successfully');
      // Optional: Show success message or redirect
    }
  }, [isSuccess]);

  const handleDelete = () => {
    if (window.confirm('Are you sure you want to delete this user?')) {
      // Simple delete without payload
      mutate();
    }
  };

  const handleDeleteWithReason = (reason) => {
    if (window.confirm('Are you sure you want to delete this user?')) {
      // Delete with optional payload
      mutate({
        reason: reason,
        deletedBy: 'current_user',
        deletedAt: new Date().toISOString()
      });
    }
  };

  return (
    <div>
      <button 
        onClick={handleDelete} 
        disabled={isLoading}
        style={{backgroundColor: 'red', color: 'white'}}
      >
        {isLoading ? 'Deleting...' : 'Delete User'}
      </button>
      
      <button 
        onClick={() => handleDeleteWithReason('User requested deletion')} 
        disabled={isLoading}
        style={{backgroundColor: 'darkred', color: 'white', marginLeft: '10px'}}
      >
        {isLoading ? 'Deleting...' : 'Delete with Reason'}
      </button>
      
      {error && <p style={{color: 'red'}}>Error: {error.message}</p>}
    </div>
  );
}
```

### useDelete - Bulk Delete with Payload
```js
import { useDelete } from 'swd-axios-query';

function BulkDeleteUsers({ selectedUserIds }) {
  const { mutate, isLoading, error, isSuccess } = useDelete(
    ['users'], // Query key for cache invalidation
    '/api/users/bulk-delete', // API endpoint for bulk operations
    {
      onSuccess: (data) => {
        console.log('Bulk delete completed:', data);
        // Show success message
      }
    }
  );

  const handleBulkDelete = () => {
    if (window.confirm(`Delete ${selectedUserIds.length} users?`)) {
      // Delete with payload containing IDs and metadata
      mutate({
        userIds: selectedUserIds,
        reason: 'Bulk cleanup',
        deletedBy: 'admin',
        deletedAt: new Date().toISOString(),
        notifyUsers: true
      });
    }
  };

  return (
    <div>
      <button 
        onClick={handleBulkDelete} 
        disabled={isLoading || selectedUserIds.length === 0}
        style={{backgroundColor: 'red', color: 'white'}}
      >
        {isLoading ? 'Deleting...' : `Delete ${selectedUserIds.length} Users`}
      </button>
      {error && <p style={{color: 'red'}}>Error: {error.message}</p>}
      {isSuccess && <p style={{color: 'green'}}>Users deleted successfully!</p>}
    </div>
  );
}
```

### Advanced Mutation Example with Custom Headers
```js
import { usePost, setContentTypeHeaders } from 'swd-axios-query';

function FileUpload() {
  const { mutate, isLoading, error, isSuccess } = usePost(
    ['files'],
    '/api/upload',
    {
      // Custom axios options for this specific request
      axiosOptions: {
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        onUploadProgress: (progressEvent) => {
          const percentCompleted = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          console.log(`Upload Progress: ${percentCompleted}%`);
        }
      }
    }
  );

  const handleFileUpload = (file) => {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('description', 'Uploaded file');

    // Direct data passing
    mutate(formData);
  };

  return (
    <div>
      <input 
        type="file" 
        onChange={(e) => {
          const file = e.target.files[0];
          if (file) handleFileUpload(file);
        }}
        disabled={isLoading}
      />
      {isLoading && <p>Uploading...</p>}
      {isSuccess && <p style={{color: 'green'}}>File uploaded successfully!</p>}
      {error && <p style={{color: 'red'}}>Upload failed: {error.message}</p>}
    </div>
  );
}
```

### Mutation with Optimistic Updates
```js
import { usePut, useQueryClient } from 'swd-axios-query';

function ToggleTodo({ todo }) {
  const queryClient = useQueryClient();
  
  const { mutate, isLoading } = usePut(
    ['todos', todo.id],
    `/api/todos/${todo.id}`,
    {
      // Optimistic update
      onMutate: async (newData) => {
        // Cancel outgoing refetches
        await queryClient.cancelQueries({ queryKey: ['todos', todo.id] });
        
        // Snapshot previous value
        const previousTodo = queryClient.getQueryData(['todos', todo.id]);
        
        // Optimistically update
        queryClient.setQueryData(['todos', todo.id], {
          ...previousTodo,
          ...newData
        });
        
        return { previousTodo };
      },
      onError: (err, newData, context) => {
        // Rollback on error
        queryClient.setQueryData(['todos', todo.id], context.previousTodo);
      },
      onSettled: () => {
        // Refetch after error or success
        queryClient.invalidateQueries({ queryKey: ['todos', todo.id] });
      }
    }
  );

  const handleToggle = () => {
    mutate({
      completed: !todo.completed,
      updatedAt: new Date().toISOString()
    });
  };

  return (
    <button 
      onClick={handleToggle} 
      disabled={isLoading}
      style={{
        textDecoration: todo.completed ? 'line-through' : 'none',
        opacity: isLoading ? 0.5 : 1
      }}
    >
      {todo.title}
    </button>
  );
}
```

---

## Core API Reference

### Configuration Functions

#### `configureSWDQuery(config)`
Configures the library with custom settings.
- **Parameters:**
  - `config` (object): `{ baseURL, headers, defaultOptions, timeout }`
- **Returns:** `void`

#### `addHeaders(headers, merge)`
Adds additional headers dynamically.
- **Parameters:**
  - `headers` (object): Headers to add
  - `merge` (boolean, optional): Whether to merge with existing headers (default: true)
- **Returns:** `void`

#### `setContentTypeHeaders(contentType, additionalHeaders)`
Sets default headers for specific content types.
- **Parameters:**
  - `contentType` (string): Content type (e.g., 'application/json', 'multipart/form-data')
  - `additionalHeaders` (object, optional): Additional headers to set
- **Returns:** `void`

#### `removeHeaders(headerKeys)`
Removes specific headers.
- **Parameters:**
  - `headerKeys` (string|Array): Header key(s) to remove
- **Returns:** `void`

#### `getHeaders()`
Gets current headers.
- **Returns:** `Object` - Current headers object

#### `axiosInstance`
Direct access to the configured Axios instance for custom requests.
- **Type:** Axios instance
- **Usage:** For requests outside of React Query hooks

#### `resetToDefaultHeaders()`
Resets headers to default values.
- **Returns:** `void`
- **Default Headers:** `Content-Type: application/json`, `Accept: application/json`

### `SWDQueryProvider`
Wrap your app to provide React Query context.
- **Props:**
  - `children` (ReactNode): The application to wrap.
  - `queryClient` (QueryClient, optional): Custom React Query client.
  - `enableDevTools` (boolean, optional): Enable React Query Devtools.

### Data Hooks
- `useGet(queryKey, queryUrl, options)`
- `usePost(queryKey, queryUrl, options)`
- `usePut(queryKey, queryUrl, options)`
- `useDelete(queryKey, queryUrl, options)`
- `useSuspenseGet(queryKey, queryUrl, options)`

**Parameters:**
- `queryKey` (array): Unique key for the query/mutation.
- `queryUrl` (string): API endpoint.
- `options` (object, optional): React Query options.

**Returns:**
- For queries: `{ data, error, isLoading, isSuccess, isError, ... }`
- For mutations: `{ mutate, mutateAsync, isLoading, error, ... }`

**Note:** This library follows official TanStack Query v5 patterns. Three approaches are supported:

### **Approach 1: useEffect with Query States (Recommended)**
```javascript
const { data, isSuccess, isError, error } = useGet(['key'], '/api');

useEffect(() => {
  if (isSuccess) {
    console.log('Success:', data);
  }
}, [isSuccess, data]);

useEffect(() => {
  if (isError) {
    console.error('Error:', error);
  }
}, [isError, error]);
```

### **Approach 2: Global Callbacks via QueryClient**
```javascript
// Set up in your app initialization
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onSuccess: (data, query) => {
        if (query.meta?.onSuccess) {
          query.meta.onSuccess(data);
        }
      },
    },
  },
});
```

### **Approach 3: meta Option with Global Callbacks**
```javascript
const { data } = useGet(['key'], '/api', {
  meta: {
    onSuccess: (data) => console.log('Success:', data),
    onError: (error) => console.error('Error:', error),
  }
});
```

---

## Advanced Usage Examples

### Header Management

#### Default Headers
The library comes with sensible defaults:
- **Content-Type**: `application/json`
- **Accept**: `application/json`

These defaults are automatically applied when you import the library. You can override them using the configuration functions.

#### Basic Header Configuration
```javascript
import { configureSWDQuery, addHeaders } from 'swd-axios-query';

// Initial configuration
configureSWDQuery({
  baseURL: 'https://api.example.com',
  headers: {
    'Authorization': 'Bearer your-token',
    'Content-Type': 'application/json'
  }
});

// Add additional headers dynamically
addHeaders({
  'X-Custom-Header': 'custom-value',
  'Accept': 'application/json'
});
```

#### Content Type Management
```javascript
import { setContentTypeHeaders, removeHeaders } from 'swd-axios-query';

// Set headers for file uploads
setContentTypeHeaders('multipart/form-data', {
  'X-Upload-Type': 'file'
});

// Set headers for JSON API
setContentTypeHeaders('application/json', {
  'Accept': 'application/json'
});

// Remove specific headers
removeHeaders(['X-Custom-Header']);
// or remove multiple
removeHeaders(['X-Custom-Header', 'X-Upload-Type']);
```

#### File Upload Example
```javascript
import { setContentTypeHeaders, axiosInstance } from 'swd-axios-query';

function FileUpload() {
  const uploadFile = async (file) => {
    // Set multipart headers for file upload
    setContentTypeHeaders('multipart/form-data');
    
    const formData = new FormData();
    formData.append('file', file);
    
    // Use axiosInstance directly for file upload
    const response = await axiosInstance.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    });
    
    return response.data;
  };

  return (
    <input 
      type="file" 
      onChange={(e) => uploadFile(e.target.files[0])} 
    />
  );
}
```

### Direct Axios Instance Usage

#### Custom API Requests
```javascript
import { axiosInstance, addHeaders } from 'swd-axios-query';

// For requests outside of React Query hooks
const customApiCall = async () => {
  // Add temporary headers
  addHeaders({ 'X-Request-ID': Date.now().toString() });
  
  try {
    const response = await axiosInstance.get('/custom-endpoint');
    return response.data;
  } catch (error) {
    console.error('Custom API call failed:', error);
    throw error;
  }
};

// Batch requests
const batchRequests = async () => {
  const [users, posts, comments] = await Promise.all([
    axiosInstance.get('/users'),
    axiosInstance.get('/posts'),
    axiosInstance.get('/comments')
  ]);
  
  return {
    users: users.data,
    posts: posts.data,
    comments: comments.data
  };
};
```

#### Interceptor Usage
```javascript
import { 
  addRequestInterceptor, 
  addResponseInterceptor,
  ejectRequestInterceptor,
  ejectResponseInterceptor 
} from 'swd-axios-query';

// Add request interceptor
const requestId = addRequestInterceptor((config) => {
  config.headers['X-Request-ID'] = Date.now().toString();
  return config;
});

// Add response interceptor
const responseId = addResponseInterceptor(
  (response) => {
    console.log('Response received:', response.status);
    return response;
  },
  (error) => {
    console.error('Request failed:', error.message);
    return Promise.reject(error);
  }
);

// Remove interceptors when needed
ejectRequestInterceptor(requestId);
ejectResponseInterceptor(responseId);
```

### Dynamic Header Management
```javascript
import { addHeaders, getHeaders, removeHeaders } from 'swd-axios-query';

// Check current headers
console.log('Current headers:', getHeaders());

// Add headers conditionally
const addAuthHeader = (token) => {
  if (token) {
    addHeaders({ 'Authorization': `Bearer ${token}` });
  }
};

// Replace all headers (don't merge)
addHeaders({
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}, false); // merge = false

// Remove authentication header
removeHeaders('Authorization');

// Reset to default headers
resetToDefaultHeaders();
```

---

## Optional & Advanced Features

### Axios Request/Response Interceptors
Add or remove Axios interceptors globally for authentication, logging, or error handling.

**Add a request interceptor:**
```js
import { addRequestInterceptor } from 'swd-axios-query';
const reqId = addRequestInterceptor(config => { /* ... */ return config; });
```
**Add a response interceptor:**
```js
import { addResponseInterceptor } from 'swd-axios-query';
const resId = addResponseInterceptor(
  response => response,
  error => Promise.reject(error)
);
```
**Remove an interceptor:**
```js
import { ejectRequestInterceptor, ejectResponseInterceptor } from 'swd-axios-query';
ejectRequestInterceptor(reqId);
ejectResponseInterceptor(resId);
```

---

### Global Loading/Error State Context (Optional)
Track loading and error state for all API requests in your app with a single provider and hook.

**Setup:**
```js
import { SWDGlobalStatusProvider } from 'swd-axios-query';
<SWDGlobalStatusProvider>
  <App />
</SWDGlobalStatusProvider>
```
**Usage:**
```js
import { useGlobalStatus } from 'swd-axios-query';
const { isLoading, error } = useGlobalStatus();
```
- This feature is optional. If you don't use the provider, nothing changes in your app.

---

## Contribution & Feedback

We welcome contributions and feedback!
- **Report bugs or request features:** Open an issue or pull request.
- **Questions or suggestions:** Contact the maintainer or open a discussion.
- **Found a security issue?** Please report it privately.

---

**Thank you for using SWD Axios Query!**

