Ultimate Guide to Sending Data in React using Axios and Fetch
Whether you're building a login form, uploading files, or submitting a complex dataset to your backend, understanding how to send data in React is essential. In this comprehensive guide, we'll explore all the major ways to send data using Axios and the native Fetch API, including formats like JSON
, form-data
, x-www-form-urlencoded
, and other advanced techniques.
This guide is designed for developers from beginner to advanced levels, providing both theoretical understanding and practical implementation examples. By the end of this tutorial, you'll have a complete understanding of how data flows from React components to backend servers, and you'll know exactly which method to choose for different scenarios.
📌 Why Learn Different Data Formats?
Before diving into implementation details, it's crucial to understand why different data formats exist and when to use each one. Each format has been developed to solve specific problems in web communication:
- JSON (application/json): The modern standard for REST APIs. It's lightweight, human-readable, and natively supported by JavaScript. Perfect for sending structured data like user profiles, configuration settings, or any complex object.
- FormData (multipart/form-data): Essential for file uploads and when your backend specifically expects multipart data. This format can handle both text fields and binary files in a single request.
- URLSearchParams (application/x-www-form-urlencoded): The traditional HTML form submission format. Still widely used by legacy systems, PHP applications, and some server frameworks that expect this specific encoding.
- Plain Text: For sending simple string data, logs, or when working with APIs that expect raw text input.
- XML: Still used in enterprise systems, SOAP services, and legacy applications that haven't migrated to JSON.
- Binary Data: For sending images, documents, or any binary content directly without form wrapping.
🔧 Setting Up Your Development Environment
Before we start sending data, let's ensure you have the right setup. For Axios, you'll need to install it in your React project:
npm install axios
# or
yarn add axios
Fetch API is built into modern browsers, so no additional installation is required. However, understanding the differences between these two approaches is important:
Axios vs Fetch API: Key Differences
Axios advantages:
- Automatic JSON parsing and stringification
- Built-in request and response interceptors
- Automatic request timeout
- Wide browser support (including IE)
- Built-in XSRF protection
- More intuitive error handling
Fetch API advantages:
- Native browser support (no additional dependencies)
- Promise-based (modern async/await support)
- Streaming support for large responses
- More control over request/response handling
- Smaller bundle size (since it's native)
1️⃣ Sending application/json
(Raw JSON)
JSON is the most common format for modern web applications. It's the default choice for REST APIs and provides excellent structure for complex data.
Deep Dive: Understanding JSON in HTTP Requests
When you send JSON data, the browser serializes your JavaScript object into a JSON string and sets the Content-Type
header to application/json
. The server then deserializes this string back into its native data structure.
Using Axios for JSON:
import axios from 'axios';
// Basic JSON request
const sendUserData = async () => {
try {
const response = await axios.post('/api/users', {
name: 'Rohan',
age: 22,
email: 'rohan@example.com',
preferences: {
theme: 'dark',
notifications: true
}
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
}
});
console.log('User created:', response.data);
return response.data;
} catch (error) {
console.error('Error creating user:', error.response?.data || error.message);
throw error;
}
};
Using Fetch for JSON:
const sendUserData = async () => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify({
name: 'Rohan',
age: 22,
email: 'rohan@example.com',
preferences: {
theme: 'dark',
notifications: true
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('User created:', data);
return data;
} catch (error) {
console.error('Error creating user:', error.message);
throw error;
}
};
Advanced JSON Handling with Custom Serialization
Sometimes you need to customize how your data is serialized. Here's how to handle special cases:
// Custom JSON serialization for dates and special values
const customStringify = (obj) => {
return JSON.stringify(obj, (key, value) => {
if (value instanceof Date) {
return value.toISOString();
}
if (typeof value === 'undefined') {
return null; // Convert undefined to null for JSON compatibility
}
return value;
});
};
const sendComplexData = async () => {
const complexData = {
user: 'John',
createdAt: new Date(),
metadata: {
source: 'web',
version: '1.2.3',
features: ['feature1', 'feature2']
}
};
const response = await fetch('/api/complex-data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: customStringify(complexData)
});
};
2️⃣ Sending multipart/form-data
(FormData)
FormData is essential when dealing with file uploads or when your server expects multipart data. This format allows you to send both text fields and binary files in a single request.
Understanding FormData Mechanics
FormData creates a set of key-value pairs representing form fields and their values. It can include files, and when sent via HTTP, it uses a special encoding that allows servers to distinguish between different parts of the data.
Using Axios for File Upload:
import axios from 'axios';
const uploadFileWithData = async (file, additionalData) => {
const formData = new FormData();
// Adding file
formData.append('profileImage', file);
// Adding text fields
formData.append('name', additionalData.name);
formData.append('description', additionalData.description);
// Adding complex data as JSON string
formData.append('metadata', JSON.stringify({
uploadedAt: new Date().toISOString(),
fileSize: file.size,
fileType: file.type
}));
try {
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`Upload Progress: ${percentCompleted}%`);
}
});
return response.data;
} catch (error) {
console.error('Upload failed:', error);
throw error;
}
};
Using Fetch for File Upload:
const uploadFileWithData = async (file, additionalData) => {
const formData = new FormData();
formData.append('profileImage', file);
formData.append('name', additionalData.name);
formData.append('description', additionalData.description);
formData.append('metadata', JSON.stringify({
uploadedAt: new Date().toISOString(),
fileSize: file.size,
fileType: file.type
}));
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData // Note: Don't set Content-Type header, let browser set it with boundary
});
if (!response.ok) {
throw new Error(`Upload failed with status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error('Upload failed:', error);
throw error;
}
};
Multiple File Upload with Progress Tracking
const uploadMultipleFiles = async (files, onProgress) => {
const formData = new FormData();
// Add multiple files
Array.from(files).forEach((file, index) => {
formData.append(`file_${index}`, file);
});
// Add metadata for each file
const filesMetadata = Array.from(files).map(file => ({
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
}));
formData.append('filesInfo', JSON.stringify(filesMetadata));
return axios.post('/api/upload-multiple', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
if (onProgress) onProgress(percentCompleted);
}
});
};
3️⃣ Sending application/x-www-form-urlencoded
This format mimics traditional HTML form submissions. It's still widely used, especially with legacy systems, PHP applications, and some server frameworks that expect this specific encoding.
Understanding URL Encoding
URL-encoded data formats key-value pairs as key1=value1&key2=value2
, with special characters properly encoded. Spaces become +
or %20
, and other special characters are percent-encoded.
Using Axios with qs library:
import axios from 'axios';
import qs from 'qs';
const sendFormData = async (formData) => {
// Simple form data
const data = qs.stringify({
username: formData.username,
password: formData.password,
rememberMe: formData.rememberMe ? 'true' : 'false'
});
try {
const response = await axios.post('/api/login', data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
return response.data;
} catch (error) {
console.error('Login failed:', error);
throw error;
}
};
// For complex nested objects
const sendComplexFormData = async () => {
const complexData = {
user: {
name: 'John',
age: 30,
hobbies: ['reading', 'coding', 'gaming']
},
settings: {
theme: 'dark',
notifications: true
}
};
const encodedData = qs.stringify(complexData, {
encode: true, // URL encode the values
arrayFormat: 'brackets' // Format arrays as name[0]=value1&name[1]=value2
});
return axios.post('/api/complex-form', encodedData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
};
Using Fetch with URLSearchParams:
const sendFormData = async (formData) => {
const params = new URLSearchParams();
params.append('username', formData.username);
params.append('password', formData.password);
params.append('rememberMe', formData.rememberMe ? 'true' : 'false');
// For arrays, append each item separately
if (formData.interests && Array.isArray(formData.interests)) {
formData.interests.forEach(interest => {
params.append('interests[]', interest);
});
}
try {
const response = await fetch('/api/form-submit', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params.toString()
});
if (!response.ok) {
throw new Error(`Form submission failed: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Form submission error:', error);
throw error;
}
};
// Helper function to convert object to URLSearchParams
const objectToURLParams = (obj, prefix = '') => {
const params = new URLSearchParams();
Object.keys(obj).forEach(key => {
const value = obj[key];
const paramKey = prefix ? `${prefix}[${key}]` : key;
if (Array.isArray(value)) {
value.forEach(item => params.append(`${paramKey}[]`, item));
} else if (typeof value === 'object' && value !== null) {
// For nested objects, you might need recursive handling
Object.keys(value).forEach(subKey => {
params.append(`${paramKey}[${subKey}]`, value[subKey]);
});
} else {
params.append(paramKey, value);
}
});
return params;
};
4️⃣ Sending text/plain
(Plain Text)
Plain text requests are useful for sending simple string data, logs, or when working with APIs that expect raw text input without any JSON or form encoding.
Common Use Cases for Plain Text
- Logging systems that accept raw log entries
- Simple notification services
- APIs that process raw text data (like some AI/ML services)
- Legacy systems that expect plain string input
// Simple text sending
const sendLogMessage = async (message) => {
try {
const response = await fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: `[${new Date().toISOString()}] ${message}`
});
if (!response.ok) {
throw new Error(`Logging failed: ${response.status}`);
}
return await response.text();
} catch (error) {
console.error('Failed to send log:', error);
}
};
// Sending formatted text data
const sendFormattedReport = async (reportData) => {
const formattedReport = `
DAILY REPORT - ${new Date().toDateString()}
================================
Total Users: ${reportData.totalUsers}
New Signups: ${reportData.newSignups}
Active Sessions: ${reportData.activeSessions}
Revenue: $${reportData.revenue.toFixed(2)}
================================
Notes: ${reportData.notes || 'No additional notes'}
`.trim();
return fetch('/api/reports/daily', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: formattedReport
});
};
5️⃣ Sending application/xml
(XML)
XML is still used in enterprise systems, SOAP web services, and legacy applications. While less common in modern web development, understanding XML handling is valuable for integration projects.
Creating and Sending XML Data
// Simple XML creation and sending
const sendXMLData = async (userData) => {
const xmlData = `
${userData.name}
${userData.email}
${userData.age}
${userData.theme}
${userData.notifications}
${new Date().toISOString()}
`;
try {
const response = await fetch('/api/xml-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/xml',
'Accept': 'application/xml'
},
body: xmlData
});
if (!response.ok) {
throw new Error(`XML request failed: ${response.status}`);
}
// Parse XML response if needed
const responseText = await response.text();
return responseText;
} catch (error) {
console.error('XML request failed:', error);
throw error;
}
};
// More complex XML with SOAP envelope
const sendSOAPRequest = async (action, data) => {
const soapEnvelope = `
${action}
${new Date().toISOString()}
<${action}Request>
${Object.keys(data).map(key =>
`<${key}>${data[key]}${key}>`
).join('\n ')}
${action}Request>
`;
return fetch('/soap-service', {
method: 'POST',
headers: {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': action
},
body: soapEnvelope
});
};
6️⃣ Sending Blob
/ ArrayBuffer
(Binary Data)
Binary data handling is crucial for applications that work with images, documents, audio files, or any non-text content. Understanding how to send binary data efficiently can significantly improve your application's performance.
Working with Different Binary Formats
// Sending a Blob (Binary Large Object)
const sendBinaryData = async (binaryData) => {
// Create blob from various sources
const blob = new Blob([binaryData], { type: 'application/octet-stream' });
try {
const response = await fetch('/api/binary-upload', {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': blob.size.toString()
},
body: blob
});
if (!response.ok) {
throw new Error(`Binary upload failed: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Binary upload failed:', error);
throw error;
}
};
// Converting image to blob and sending
const sendImageAsBlob = async (imageFile) => {
// Convert image to different format if needed
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
return new Promise((resolve, reject) => {
img.onload = async () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// Convert to blob with specific quality/format
canvas.toBlob(async (blob) => {
try {
const response = await fetch('/api/image-upload', {
method: 'POST',
headers: {
'Content-Type': 'image/jpeg'
},
body: blob
});
const result = await response.json();
resolve(result);
} catch (error) {
reject(error);
}
}, 'image/jpeg', 0.8); // 80% quality JPEG
};
img.onerror = reject;
img.src = URL.createObjectURL(imageFile);
});
};
// Working with ArrayBuffer
const sendArrayBuffer = async (arrayBuffer) => {
return fetch('/api/arraybuffer-upload', {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream'
},
body: arrayBuffer
});
};
// Reading file as ArrayBuffer and sending
const processAndSendFile = async (file) => {
const arrayBuffer = await file.arrayBuffer();
// Process the binary data if needed
const uint8Array = new Uint8Array(arrayBuffer);
// Example: Simple checksum calculation
let checksum = 0;
for (let i = 0; i < uint8Array.length; i++) {
checksum += uint8Array[i];
}
// Send with metadata
const metadata = {
filename: file.name,
size: file.size,
checksum: checksum,
lastModified: file.lastModified
};
return fetch('/api/file-with-metadata', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-File-Checksum': checksum.toString(),
'X-File-Size': file.size.toString()
},
body: JSON.stringify({
metadata: metadata,
data: Array.from(uint8Array) // Convert to array for JSON serialization
})
});
};
7️⃣ Using navigator.sendBeacon
(Background Reporting)
The Beacon API is designed for sending small amounts of data to servers in a reliable way, even when the page is being unloaded. It's perfect for analytics, error reporting, and logging.
Understanding Beacon API Benefits
- Reliable delivery even during page unload
- Non-blocking (doesn't delay page navigation)
- Queued by the browser for optimal delivery
- Limited data size (64KB typically)
// Basic beacon usage for analytics
const sendAnalyticsData = (eventData) => {
const data = JSON.stringify({
event: eventData.event,
timestamp: Date.now(),
userId: eventData.userId,
sessionId: eventData.sessionId,
properties: eventData.properties
});
const blob = new Blob([data], { type: 'application/json' });
if (navigator.sendBeacon('/api/analytics', blob)) {
console.log('Analytics data queued for sending');
} else {
console.warn('Failed to queue analytics data');
// Fallback to regular fetch
fetch('/api/analytics', {
method: 'POST',
body: blob,
keepalive: true // Important for page unload scenarios
}).catch(console.error);
}
};
// Page visibility change tracking
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
sendAnalyticsData({
event: 'page_hidden',
userId: getCurrentUserId(),
sessionId: getSessionId(),
properties: {
timeOnPage: Date.now() - pageStartTime,
scrollDepth: getScrollDepth(),
interactions: getInteractionCount()
}
});
}
});
// Error reporting with beacon
window.addEventListener('error', (event) => {
const errorData = {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
userAgent: navigator.userAgent,
timestamp: Date.now(),
url: window.location.href
};
const blob = new Blob([JSON.stringify(errorData)], { type: 'application/json' });
navigator.sendBeacon('/api/errors', blob);
});
// Performance metrics reporting
const reportPerformanceMetrics = () => {
if ('performance' in window && 'getEntriesByType' in performance) {
const navigation = performance.getEntriesByType('navigation')[0];
const metrics = {
loadTime: navigation.loadEventEnd - navigation.loadEventStart,
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
firstPaint: performance.getEntriesByType('paint').find(p => p.name === 'first-paint')?.startTime,
firstContentfulPaint: performance.getEntriesByType('paint').find(p => p.name === 'first-contentful-paint')?.startTime
};
const blob = new Blob([JSON.stringify(metrics)], { type: 'application/json' });
navigator.sendBeacon('/api/performance', blob);
}
};
8️⃣ Sending Data with WebSockets
WebSockets provide full-duplex communication between client and server, enabling real-time data exchange. Unlike HTTP requests, WebSocket connections remain open, allowing for instant bidirectional communication.
Setting Up WebSocket Communication
class WebSocketManager {
constructor(url) {
this.url = url;
this.socket = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
this.messageQueue = [];
}
connect() {
return new Promise((resolve, reject) => {
this.socket = new WebSocket(this.url);
this.socket.onopen = (event) => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
// Send queued messages
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.socket.send(JSON.stringify(message));
}
resolve(event);
};
this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleMessage(data);
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
this.socket.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.handleReconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
});
}
send(data) {
const message = {
id: this.generateMessageId(),
timestamp: Date.now(),
data: data
};
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
} else {
// Queue message if not connected
this.messageQueue.push(message);
console.log('Message queued (WebSocket not connected)');
}
}
handleMessage(data) {
// Override this method to handle incoming messages
console.log('Received:', data);
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
setTimeout(() => {
this.connect().catch(console.error);
}, this.reconnectDelay * this.reconnectAttempts);
} else {
console.error('Max reconnection attempts reached');
}
}
generateMessageId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
close() {
if (this.socket) {
this.socket.close();
}
}
}
// Usage example
const wsManager = new WebSocketManager('ws://localhost:8080');
wsManager.handleMessage = (data) => {
switch (data.type) {
case 'user_message':
displayChatMessage(data.payload);
break;
case 'notification':
showNotification(data.payload);
break;
case 'system_update':
handleSystemUpdate(data.payload);
break;
default:
console.log('Unknown message type:', data.type);
}
};
// Connect and start sending data
wsManager.connect().then(() => {
// Send different types of data
wsManager.send({
type: 'user_join',
payload: {
userId: 'user123',
username: 'JohnDoe',
room: 'general'
}
});
// Send chat message
wsManager.send({
type: 'chat_message',
payload: {
message: 'Hello everyone!',
timestamp: Date.now()
}
});
// Send typing indicator
wsManager.send({
type: 'typing_start',
payload: {
userId: 'user123'
}
});
}).catch(console.error);
9️⃣ Sending GraphQL Queries
GraphQL is a query language and runtime for APIs that allows clients to request exactly the data they need. Unlike REST APIs, GraphQL uses a single endpoint and allows for more flexible data fetching.
Understanding GraphQL Request Structure
GraphQL requests are always HTTP POST requests sent to a single endpoint. The request body contains a JSON object with a query
field (and optionally variables
and operationName
fields).
// Basic GraphQL query
const fetchUserData = async (userId) => {
const query = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
profile {
bio
avatar
createdAt
}
posts(first: 10) {
edges {
node {
id
title
content
createdAt
}
}
}
}
}
`;
const variables = { id: userId };
try {
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`
},
body: JSON.stringify({
query,
variables
})
});
const result = await response.json();
if (result.errors) {
throw new Error(`GraphQL errors: ${result.errors.map(e => e.message).join(', ')}`);
}
return result.data;
} catch (error) {
console.error('GraphQL query failed:', error);
throw error;
}
};
// GraphQL mutation
const createUserPost = async (postData) => {
const mutation = `
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
content
author {
id
name
}
createdAt
updatedAt
}
}
`;
const variables = {
input: {
title: postData.title,
content: postData.content,
tags: postData.tags || [],
published: postData.published || false
}
};
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`
},
body: JSON.stringify({
query: mutation,
variables,
operationName: 'CreatePost'
})
});
const result = await response.json();
if (result.errors) {
throw new Error(`Mutation failed: ${result.errors.map(e => e.message).join(', ')}`);
}
return result.data.createPost;
};
// GraphQL subscription (typically used with WebSockets)
const subscribeToComments = (postId) => {
const subscription = `
subscription CommentAdded($postId: ID!) {
commentAdded(postId: $postId) {
id
content
author {
id
name
}
createdAt
}
}
`;
// This would typically use a WebSocket connection
// For HTTP-based subscriptions, you might use Server-Sent Events
const eventSource = new EventSource(`/graphql-subscriptions?query=${encodeURIComponent(subscription)}&variables=${encodeURIComponent(JSON.stringify({ postId }))}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
handleNewComment(data.commentAdded);
};
return eventSource;
};
// Advanced GraphQL client with caching
class GraphQLClient {
constructor(endpoint, options = {}) {
this.endpoint = endpoint;
this.defaultHeaders = options.headers || {};
this.cache = new Map();
this.cacheTimeout = options.cacheTimeout || 5 * 60 * 1000; // 5 minutes
}
async query(query, variables = {}, options = {}) {
const cacheKey = this.getCacheKey(query, variables);
// Check cache first
if (!options.skipCache && this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
}
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.defaultHeaders,
...options.headers
},
body: JSON.stringify({
query,
variables,
operationName: options.operationName
})
});
const result = await response.json();
if (result.errors) {
throw new GraphQLError(result.errors);
}
// Cache successful queries
if (!options.skipCache && result.data) {
this.cache.set(cacheKey, {
data: result.data,
timestamp: Date.now()
});
}
return result.data;
}
async mutate(mutation, variables = {}, options = {}) {
// Mutations should not be cached and should invalidate related cache entries
const response = await this.query(mutation, variables, { ...options, skipCache: true });
// Invalidate cache entries that might be affected
if (options.invalidateCache) {
options.invalidateCache.forEach(pattern => {
this.invalidateCache(pattern);
});
}
return response;
}
getCacheKey(query, variables) {
return btoa(JSON.stringify({ query, variables }));
}
invalidateCache(pattern) {
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key);
}
}
}
}
class GraphQLError extends Error {
constructor(errors) {
super(errors.map(e => e.message).join(', '));
this.graphQLErrors = errors;
}
}
🔟 Using Axios Alternatives: ky, superagent, and others
While Axios is popular, there are several other HTTP client libraries, each with unique features and benefits. Understanding these alternatives can help you choose the right tool for specific use cases.
Ky - Modern Fetch Alternative
Ky is a tiny and elegant HTTP client based on the browser Fetch API with many convenient features.
import ky from 'ky';
// Basic usage
const sendDataWithKy = async () => {
try {
const user = await ky.post('/api/users', {
json: {
name: 'Rohan',
email: 'rohan@example.com',
age: 22
}
}).json();
return user;
} catch (error) {
if (error.response) {
const errorData = await error.response.json();
console.error('Server error:', errorData);
}
throw error;
}
};
// With retry and timeout
const robustRequest = async () => {
return ky.post('/api/data', {
json: { message: 'Hello World' },
retry: {
limit: 3,
statusCodes: [408, 413, 429, 500, 502, 503, 504]
},
timeout: 10000, // 10 seconds
hooks: {
beforeRequest: [
request => {
console.log('Sending request:', request.url);
request.headers.set('X-Custom-Header', 'custom-value');
}
],
afterResponse: [
(_request, _options, response) => {
console.log('Response received:', response.status);
return response;
}
]
}
}).json();
};
// File upload with ky
const uploadWithKy = async (file) => {
const formData = new FormData();
formData.append('file', file);
formData.append('metadata', JSON.stringify({
uploadedAt: new Date().toISOString(),
originalName: file.name
}));
return ky.post('/api/upload', {
body: formData
}).json();
};
Superagent - Feature-Rich HTTP Client
import superagent from 'superagent';
// Basic POST request
const sendWithSuperagent = async () => {
try {
const response = await superagent
.post('/api/users')
.send({
name: 'Rohan',
email: 'rohan@example.com'
})
.set('Authorization', 'Bearer token')
.timeout({
response: 5000, // 5 seconds to wait for server response
deadline: 60000 // 60 seconds for entire request
});
return response.body;
} catch (error) {
console.error('Superagent error:', error);
throw error;
}
};
// File upload with progress
const uploadWithProgress = (file, onProgress) => {
return new Promise((resolve, reject) => {
superagent
.post('/api/upload')
.attach('file', file)
.field('name', 'document')
.field('description', 'Important document')
.on('progress', (event) => {
if (onProgress && event.direction === 'upload') {
onProgress(event.percent);
}
})
.end((error, response) => {
if (error) {
reject(error);
} else {
resolve(response.body);
}
});
});
};
// Chained requests
const chainedRequests = async () => {
const user = await superagent
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' });
const profile = await superagent
.put(`/api/users/${user.body.id}/profile`)
.send({ bio: 'Developer', location: 'San Francisco' });
return { user: user.body, profile: profile.body };
};
🔧 Advanced Techniques and Best Practices
Request Interceptors and Middleware
Interceptors allow you to automatically process requests and responses, adding authentication, logging, error handling, and more.
// Axios interceptors
import axios from 'axios';
const apiClient = axios.create({
baseURL: '/api',
timeout: 10000
});
// Request interceptor
apiClient.interceptors.request.use(
(config) => {
// Add auth token
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Add request ID for tracking
config.headers['X-Request-ID'] = generateRequestId();
// Log request
console.log('Request:', config.method?.toUpperCase(), config.url);
return config;
},
(error) => {
console.error('Request error:', error);
return Promise.reject(error);
}
);
// Response interceptor
apiClient.interceptors.response.use(
(response) => {
// Log successful responses
console.log('Response:', response.status, response.config.url);
return response;
},
async (error) => {
const originalRequest = error.config;
// Handle token refresh
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await refreshAuthToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
// Redirect to login
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
// Handle rate limiting
if (error.response?.status === 429) {
const retryAfter = error.response.headers['retry-after'] || 1;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return apiClient(originalRequest);
}
return Promise.reject(error);
}
);
// Fetch interceptor equivalent
class FetchInterceptor {
constructor(baseURL = '') {
this.baseURL = baseURL;
this.requestInterceptors = [];
this.responseInterceptors = [];
}
addRequestInterceptor(interceptor) {
this.requestInterceptors.push(interceptor);
}
addResponseInterceptor(interceptor) {
this.responseInterceptors.push(interceptor);
}
async fetch(url, options = {}) {
let finalOptions = { ...options };
let finalUrl = url.startsWith('http') ? url : this.baseURL + url;
// Apply request interceptors
for (const interceptor of this.requestInterceptors) {
const result = await interceptor({ url: finalUrl, ...finalOptions });
if (result) {
finalUrl = result.url || finalUrl;
finalOptions = result;
}
}
try {
let response = await fetch(finalUrl, finalOptions);
// Apply response interceptors
for (const interceptor of this.responseInterceptors) {
response = await interceptor(response, { url: finalUrl, ...finalOptions }) || response;
}
return response;
} catch (error) {
// Handle network errors
throw error;
}
}
}
const fetchClient = new FetchInterceptor('/api');
fetchClient.addRequestInterceptor(async (config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`
};
}
return config;
});
fetchClient.addResponseInterceptor(async (response) => {
if (response.status === 401) {
// Handle unauthorized
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return response;
});
Error Handling and Retry Logic
// Comprehensive error handling
class APIError extends Error {
constructor(message, status, response) {
super(message);
this.status = status;
this.response = response;
this.name = 'APIError';
}
}
const handleAPIError = (error) => {
if (error.response) {
// Server responded with error status
switch (error.response.status) {
case 400:
throw new APIError('Bad Request - Invalid data sent', 400, error.response);
case 401:
throw new APIError('Unauthorized - Please login', 401, error.response);
case 403:
throw new APIError('Forbidden - Insufficient permissions', 403, error.response);
case 404:
throw new APIError('Not Found - Resource does not exist', 404, error.response);
case 429:
throw new APIError('Too Many Requests - Please slow down', 429, error.response);
case 500:
throw new APIError('Internal Server Error - Please try again later', 500, error.response);
default:
throw new APIError(`HTTP ${error.response.status} Error`, error.response.status, error.response);
}
} else if (error.request) {
// Network error
throw new APIError('Network Error - Please check your connection', 0);
} else {
// Other error
throw new APIError(error.message || 'Unknown Error');
}
};
// Retry logic with exponential backoff
const retryRequest = async (requestFn, maxRetries = 3, baseDelay = 1000) => {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
lastError = error;
// Don't retry client errors (4xx) except 429
if (error.status && error.status >= 400 && error.status < 500 && error.status !== 429) {
throw error;
}
if (attempt === maxRetries) {
break;
}
// Exponential backoff with jitter
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
console.log(`Retry attempt ${attempt + 1} in ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
};
// Usage example
const robustAPICall = async (userData) => {
return retryRequest(async () => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) {
throw { response };
}
return await response.json();
} catch (error) {
handleAPIError(error);
}
});
};
Performance Optimization Techniques
// Request deduplication
class RequestDeduplicator {
constructor() {
this.pendingRequests = new Map();
}
async request(key, requestFn) {
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
const promise = requestFn()
.finally(() => {
this.pendingRequests.delete(key);
});
this.pendingRequests.set(key, promise);
return promise;
}
}
const deduplicator = new RequestDeduplicator();
// Usage
const fetchUser = (userId) => {
return deduplicator.request(`user-${userId}`, async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
};
// Batch requests
class RequestBatcher {
constructor(endpoint, delay = 100) {
this.endpoint = endpoint;
this.delay = delay;
this.batch = [];
this.timeoutId = null;
}
add(data) {
return new Promise((resolve, reject) => {
this.batch.push({ data, resolve, reject });
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.flush();
}, this.delay);
});
}
async flush() {
if (this.batch.length === 0) return;
const currentBatch = this.batch.slice();
this.batch = [];
this.timeoutId = null;
try {
const response = await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
requests: currentBatch.map(item => item.data)
})
});
const results = await response.json();
currentBatch.forEach((item, index) => {
item.resolve(results[index]);
});
} catch (error) {
currentBatch.forEach(item => {
item.reject(error);
});
}
}
}
// Usage
const batcher = new RequestBatcher('/api/batch');
// These will be batched together
const result1 = batcher.add({ action: 'getUser', id: 1 });
const result2 = batcher.add({ action: 'getUser', id: 2 });
const result3 = batcher.add({ action: 'updateStatus', status: 'active' });
// Request cancellation with AbortController
const createCancellableRequest = () => {
const controller = new AbortController();
const request = fetch('/api/data', {
signal: controller.signal,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: 'example' })
});
return {
request,
cancel: () => controller.abort()
};
};
// Usage in React component
const useCancellableRequest = () => {
const [controller, setController] = useState(null);
const sendRequest = useCallback(async (data) => {
// Cancel previous request if exists
if (controller) {
controller.abort();
}
const newController = new AbortController();
setController(newController);
try {
const response = await fetch('/api/data', {
signal: newController.signal,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request cancelled');
return null;
}
throw error;
}
}, [controller]);
useEffect(() => {
return () => {
if (controller) {
controller.abort();
}
};
}, [controller]);
return sendRequest;
};
Security Considerations
// CSRF Token handling
const getCSRFToken = () => {
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
if (!token) {
console.warn('CSRF token not found');
}
return token;
};
const secureRequest = async (url, data) => {
const csrfToken = getCSRFToken();
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin', // Include cookies
body: JSON.stringify(data)
});
};
// Input sanitization
const sanitizeInput = (input) => {
if (typeof input !== 'string') return input;
return input
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
};
// Validate data before sending
const validateAndSend = async (userData) => {
// Validation rules
const rules = {
name: {
required: true,
minLength: 2,
maxLength: 50,
pattern: /^[a-zA-Z\s]+$/
},
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
age: {
required: true,
min: 13,
max: 120,
type: 'number'
}
};
const errors = [];
Object.keys(rules).forEach(field => {
const rule = rules[field];
const value = userData[field];
if (rule.required && (!value || value.toString().trim() === '')) {
errors.push(`${field} is required`);
return;
}
if (value) {
if (rule.type === 'number' && isNaN(Number(value))) {
errors.push(`${field} must be a number`);
}
if (rule.minLength && value.toString().length < rule.minLength) {
errors.push(`${field} must be at least ${rule.minLength} characters`);
}
if (rule.maxLength && value.toString().length > rule.maxLength) {
errors.push(`${field} must be no more than ${rule.maxLength} characters`);
}
if (rule.pattern && !rule.pattern.test(value.toString())) {
errors.push(`${field} has invalid format`);
}
if (rule.min && Number(value) < rule.min) {
errors.push(`${field} must be at least ${rule.min}`);
}
if (rule.max && Number(value) > rule.max) {
errors.push(`${field} must be no more than ${rule.max}`);
}
}
});
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
// Sanitize string inputs
const sanitizedData = Object.keys(userData).reduce((acc, key) => {
acc[key] = typeof userData[key] === 'string'
? sanitizeInput(userData[key])
: userData[key];
return acc;
}, {});
return secureRequest('/api/users', sanitizedData);
};
🧠Comprehensive Summary and Decision Guide
Choosing the right method for sending data in React depends on several factors. Here's a comprehensive decision matrix to help you choose:
📊 Format Selection Guide
- Use JSON when: Building modern REST APIs, sending structured data, working with JavaScript objects, need easy parsing on both ends
- Use FormData when: Uploading files, working with multipart data, backend expects form-data, dealing with mixed content types
- Use URLEncoded when: Working with legacy systems, PHP backends that expect form data, traditional HTML form submission compatibility
- Use Plain Text when: Sending simple strings, logging systems, APIs that process raw text, minimal overhead required
- Use XML when: Integrating with SOAP services, legacy enterprise systems, specific industry standards require XML
- Use Binary when: Sending images directly, working with binary protocols, maximum efficiency for file transfers
🔧 Library Selection Guide
- Use Fetch when: Building modern applications, want native browser support, need streaming capabilities, minimal bundle size is important
- Use Axios when: Need request/response interceptors, want automatic JSON handling, require IE support, prefer more features out of the box
- Use Ky when: Want modern fetch-based solution, need retry logic, prefer functional approach, working with modern browsers only
- Use Superagent when: Need fluent API, require advanced features, want detailed progress tracking, working on Node.js backend too
🔥 Advanced Tips for Production Applications
- Always implement proper error handling: Network errors, server errors, validation errors, and timeout handling
- Use request deduplication: Prevent duplicate requests when users click buttons multiple times
- Implement retry logic: Handle temporary failures gracefully with exponential backoff
- Add request/response logging: Essential for debugging and monitoring in production
- Handle loading states: Provide visual feedback to users during API calls
- Implement request cancellation: Cancel ongoing requests when components unmount
- Use HTTPS everywhere: Ensure all data transmission is encrypted
- Validate input data: Both client-side and server-side validation are essential
- Implement proper authentication: Use tokens, handle token refresh, secure storage
- Monitor performance: Track request times, success rates, and error patterns
💬 Troubleshooting Common Issues
CORS Errors: Configure your server to allow cross-origin requests, or use a proxy during development.
Network Timeout: Implement proper timeout handling and show appropriate user feedback.
Large File Uploads: Consider chunked uploads, progress indicators, and resume capability.
Authentication Issues: Implement token refresh logic and proper error handling for expired tokens.
Memory Leaks: Always clean up event listeners, cancel requests, and clear timers in useEffect cleanup.
This comprehensive guide covers all the essential techniques for sending data in React applications. Whether you're building a simple contact form or a complex file upload system, these patterns and techniques will help you create robust, efficient, and secure data transmission solutions.
Remember to always consider your specific use case, target browsers, performance requirements, and security needs when choosing your approach. Happy coding!
Comments
Post a Comment