useConnectionManager
Overview
The useConnectionManager
hook is a comprehensive connection state management solution that combines connection status checking, sending requests, accepting, declining, and removing connections into a single, powerful interface. It automatically manages complex connection states and provides intuitive methods for all connection-related operations.
Usage Example
import { useConnectionManager } from "@replyke/react-js";
function ConnectionButton({ userId }: { userId: string }) {
const {
connectionStatus,
connectionData,
isLoading,
sendConnectionRequest,
acceptConnectionRequest,
declineConnectionRequest,
withdrawConnectionRequest,
disconnectUser,
refreshConnectionStatus
} = useConnectionManager({ userId });
if (isLoading) {
return <button disabled>Loading...</button>;
}
switch (connectionStatus) {
case 'none':
return (
<button onClick={() => sendConnectionRequest()}>
Connect
</button>
);
case 'pending-sent':
return (
<button onClick={withdrawConnectionRequest}>
Withdraw Request
</button>
);
case 'pending-received':
return (
<div className="pending-actions">
<button onClick={acceptConnectionRequest}>Accept</button>
<button onClick={declineConnectionRequest}>Decline</button>
</div>
);
case 'connected':
return (
<button onClick={disconnectUser} className="disconnect-btn">
Disconnect
</button>
);
case 'declined-sent':
return <button disabled>Request Declined</button>;
case 'declined-received':
return (
<button onClick={() => sendConnectionRequest()}>
Send New Request
</button>
);
default:
return <button disabled>Unknown Status</button>;
}
}
Advanced Usage with Complete UI State
import { useConnectionManager } from "@replyke/react-js";
import { useState } from "react";
function EnhancedConnectionCard({
userId,
username,
displayName,
avatar
}: {
userId: string;
username: string;
displayName?: string;
avatar?: string;
}) {
const {
connectionStatus,
connectionData,
isLoading,
sendConnectionRequest,
acceptConnectionRequest,
declineConnectionRequest,
withdrawConnectionRequest,
disconnectUser,
refreshConnectionStatus
} = useConnectionManager({ userId });
const [showMessageForm, setShowMessageForm] = useState(false);
const [connectionMessage, setConnectionMessage] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const handleSendRequest = async () => {
if (showMessageForm && connectionMessage.trim()) {
setIsProcessing(true);
try {
await sendConnectionRequest(connectionMessage.trim());
setShowMessageForm(false);
setConnectionMessage("");
} catch (error) {
console.error("Failed to send connection request:", error);
alert("Failed to send connection request. Please try again.");
} finally {
setIsProcessing(false);
}
} else if (!showMessageForm) {
setShowMessageForm(true);
} else {
// Send without message
setIsProcessing(true);
try {
await sendConnectionRequest();
setShowMessageForm(false);
} catch (error) {
console.error("Failed to send connection request:", error);
alert("Failed to send connection request. Please try again.");
} finally {
setIsProcessing(false);
}
}
};
const handleConnectionAction = async (action: () => Promise<void>, actionName: string) => {
setIsProcessing(true);
try {
await action();
console.log(`Successfully ${actionName} for ${username}`);
} catch (error) {
console.error(`Failed to ${actionName}:`, error);
alert(`Failed to ${actionName}. Please try again.`);
} finally {
setIsProcessing(false);
}
};
const getStatusBadge = () => {
switch (connectionStatus) {
case 'connected':
return <span className="status-badge connected">Connected</span>;
case 'pending-sent':
return <span className="status-badge pending">Request Sent</span>;
case 'pending-received':
return <span className="status-badge pending">Pending Response</span>;
case 'declined-sent':
return <span className="status-badge declined">Request Declined</span>;
case 'declined-received':
return <span className="status-badge declined">Request Declined</span>;
default:
return null;
}
};
const getConnectionDate = () => {
if (connectionStatus === 'connected' && connectionData.connectedAt) {
return `Connected ${new Date(connectionData.connectedAt).toLocaleDateString()}`;
}
if ((connectionStatus === 'pending-sent' || connectionStatus === 'pending-received') && connectionData.createdAt) {
return `Requested ${new Date(connectionData.createdAt).toLocaleDateString()}`;
}
return null;
};
return (
<div className="enhanced-connection-card">
<div className="card-header">
<div className="user-info">
{avatar && (
<img src={avatar} alt={`${username} avatar`} className="avatar" />
)}
<div className="user-details">
<h3>{displayName || username}</h3>
<p>@{username}</p>
{getConnectionDate() && (
<small className="connection-date">{getConnectionDate()}</small>
)}
</div>
</div>
{getStatusBadge()}
</div>
{isLoading ? (
<div className="loading-state">
<span>Loading connection status...</span>
</div>
) : (
<div className="connection-actions">
{showMessageForm && connectionStatus === 'none' && (
<div className="message-form">
<textarea
value={connectionMessage}
onChange={(e) => setConnectionMessage(e.target.value)}
placeholder={`Send a message to ${displayName || username}...`}
maxLength={500}
rows={3}
/>
<div className="message-actions">
<button
onClick={() => setShowMessageForm(false)}
disabled={isProcessing}
className="cancel-btn"
>
Cancel
</button>
<button
onClick={handleSendRequest}
disabled={isProcessing}
className="send-btn"
>
{isProcessing ? "Sending..." : "Send Request"}
</button>
</div>
</div>
)}\n\n {!showMessageForm && (\n <div className=\"action-buttons\">\n {connectionStatus === 'none' && (\n <button\n onClick={handleSendRequest}\n disabled={isProcessing}\n className=\"connect-btn primary\"\n >\n {isProcessing ? \"Connecting...\" : \"Connect\"}\n </button>\n )}\n\n {connectionStatus === 'pending-sent' && (\n <button\n onClick={() => handleConnectionAction(withdrawConnectionRequest, \"withdrew request\")}\n disabled={isProcessing}\n className=\"withdraw-btn secondary\"\n >\n {isProcessing ? \"Withdrawing...\" : \"Withdraw Request\"}\n </button>\n )}\n\n {connectionStatus === 'pending-received' && (\n <>\n <button\n onClick={() => handleConnectionAction(acceptConnectionRequest, \"accepted connection\")}\n disabled={isProcessing}\n className=\"accept-btn primary\"\n >\n {isProcessing ? \"Accepting...\" : \"Accept\"}\n </button>\n <button\n onClick={() => handleConnectionAction(declineConnectionRequest, \"declined connection\")}\n disabled={isProcessing}\n className=\"decline-btn secondary\"\n >\n {isProcessing ? \"Declining...\" : \"Decline\"}\n </button>\n </>\n )}\n\n {connectionStatus === 'connected' && (\n <button\n onClick={() => handleConnectionAction(disconnectUser, \"disconnected\")}\n disabled={isProcessing}\n className=\"disconnect-btn danger\"\n >\n {isProcessing ? \"Disconnecting...\" : \"Disconnect\"}\n </button>\n )}\n\n {connectionStatus === 'declined-received' && (\n <button\n onClick={handleSendRequest}\n disabled={isProcessing}\n className=\"connect-btn primary\"\n >\n {isProcessing ? \"Sending...\" : \"Send New Request\"}\n </button>\n )}\n\n {connectionStatus === 'declined-sent' && (\n <button disabled className=\"declined-btn disabled\">\n Request Declined\n </button>\n )}\n </div>\n )}\n\n <button\n onClick={refreshConnectionStatus}\n className=\"refresh-btn\"\n title=\"Refresh connection status\"\n >\n ⟳ Refresh\n </button>\n </div>\n )}\n </div>\n );\n}\n```\n\n## Usage in User Profile Page\n\n```tsx\nimport { useConnectionManager } from \"@replyke/react-js\";\nimport { useState } from \"react\";\n\ninterface UserProfile {\n id: string;\n username: string;\n displayName?: string;\n avatar?: string;\n bio?: string;\n location?: string;\n website?: string;\n}\n\nfunction UserProfileConnectionSection({ user }: { user: UserProfile }) {\n const {\n connectionStatus,\n connectionData,\n isLoading,\n sendConnectionRequest,\n acceptConnectionRequest,\n declineConnectionRequest,\n withdrawConnectionRequest,\n disconnectUser,\n removeConnectionSmart\n } = useConnectionManager({ userId: user.id });\n\n const [showConfirmDialog, setShowConfirmDialog] = useState(false);\n const [pendingAction, setPendingAction] = useState<string | null>(null);\n\n const handleActionWithConfirmation = (action: () => Promise<void>, actionName: string, message: string) => {\n setPendingAction(actionName);\n if (window.confirm(message)) {\n action().then(() => {\n console.log(`Successfully ${actionName}`);\n }).catch((error) => {\n console.error(`Failed to ${actionName}:`, error);\n alert(`Failed to ${actionName}. Please try again.`);\n }).finally(() => {\n setPendingAction(null);\n });\n } else {\n setPendingAction(null);\n }\n };\n\n const getConnectionInsight = () => {\n switch (connectionStatus) {\n case 'connected':\n if (connectionData.connectedAt && connectionData.requestedAt) {\n const requestDate = new Date(connectionData.requestedAt);\n const connectedDate = new Date(connectionData.connectedAt);\n const daysBetween = Math.ceil((connectedDate.getTime() - requestDate.getTime()) / (1000 * 60 * 60 * 24));\n return `Connected ${connectedDate.toLocaleDateString()}${daysBetween > 0 ? ` (${daysBetween} days after request)` : ''}`;\n }\n return \"Connected\";\n case 'pending-sent':\n if (connectionData.createdAt) {\n const daysSince = Math.ceil((Date.now() - new Date(connectionData.createdAt).getTime()) / (1000 * 60 * 60 * 24));\n return `Request sent ${daysSince === 0 ? 'today' : `${daysSince} day${daysSince === 1 ? '' : 's'} ago`}`;\n }\n return \"Request sent\";\n case 'pending-received':\n if (connectionData.createdAt) {\n const daysSince = Math.ceil((Date.now() - new Date(connectionData.createdAt).getTime()) / (1000 * 60 * 60 * 24));\n return `Request received ${daysSince === 0 ? 'today' : `${daysSince} day${daysSince === 1 ? '' : 's'} ago`}`;\n }\n return \"Request received\";\n case 'declined-sent':\n return \"Your request was declined\";\n case 'declined-received':\n return \"You can send a new request\";\n default:\n return \"Not connected\";\n }\n };\n\n return (\n <div className=\"user-profile-connection-section\">\n <div className=\"connection-status\">\n <h4>Connection Status</h4>\n <p className={`status-text ${connectionStatus}`}>\n {isLoading ? \"Loading...\" : getConnectionInsight()}\n </p>\n </div>\n\n <div className=\"connection-actions\">\n {connectionStatus === 'none' && (\n <button\n onClick={() => sendConnectionRequest(`I'd like to connect with you, ${user.displayName || user.username}!`)}\n disabled={pendingAction !== null}\n className=\"connect-btn primary\"\n >\n {pendingAction === 'connect' ? \"Connecting...\" : \"Send Connection Request\"}\n </button>\n )}\n\n {connectionStatus === 'pending-sent' && (\n <button\n onClick={() => handleActionWithConfirmation(\n withdrawConnectionRequest,\n \"withdraw\",\n `Are you sure you want to withdraw your connection request to ${user.displayName || user.username}?`\n )}\n disabled={pendingAction !== null}\n className=\"withdraw-btn secondary\"\n >\n {pendingAction === 'withdraw' ? \"Withdrawing...\" : \"Withdraw Request\"}\n </button>\n )}\n\n {connectionStatus === 'pending-received' && (\n <div className=\"pending-received-actions\">\n <button\n onClick={() => acceptConnectionRequest()}\n disabled={pendingAction !== null}\n className=\"accept-btn primary\"\n >\n {pendingAction === 'accept' ? \"Accepting...\" : \"Accept Request\"}\n </button>\n <button\n onClick={() => handleActionWithConfirmation(\n declineConnectionRequest,\n \"decline\",\n `Are you sure you want to decline the connection request from ${user.displayName || user.username}?`\n )}\n disabled={pendingAction !== null}\n className=\"decline-btn secondary\"\n >\n {pendingAction === 'decline' ? \"Declining...\" : \"Decline Request\"}\n </button>\n </div>\n )}\n\n {connectionStatus === 'connected' && (\n <div className=\"connected-actions\">\n <button\n onClick={() => handleActionWithConfirmation(\n disconnectUser,\n \"disconnect\",\n `Are you sure you want to disconnect from ${user.displayName || user.username}? This will remove the connection between you.`\n )}\n disabled={pendingAction !== null}\n className=\"disconnect-btn danger\"\n >\n {pendingAction === 'disconnect' ? \"Disconnecting...\" : \"Disconnect\"}\n </button>\n \n <button\n onClick={() => handleActionWithConfirmation(\n removeConnectionSmart,\n \"remove\",\n `Are you sure you want to remove all connection data with ${user.displayName || user.username}?`\n )}\n disabled={pendingAction !== null}\n className=\"remove-btn danger\"\n >\n {pendingAction === 'remove' ? \"Removing...\" : \"Remove Connection\"}\n </button>\n </div>\n )}\n\n {connectionStatus === 'declined-received' && (\n <button\n onClick={() => sendConnectionRequest(`I'd still like to connect with you, ${user.displayName || user.username}.`)}\n disabled={pendingAction !== null}\n className=\"connect-btn primary\"\n >\n {pendingAction === 'connect' ? \"Sending...\" : \"Send New Request\"}\n </button>\n )}\n </div>\n </div>\n );\n}\n```\n\n## Usage in Connections Dashboard\n\n```tsx\nimport { useConnectionManager } from \"@replyke/react-js\";\nimport { useState, useEffect } from \"react\";\n\ninterface ConnectionItem {\n userId: string;\n username: string;\n displayName?: string;\n avatar?: string;\n}\n\nfunction ConnectionsDashboard({ connections }: { connections: ConnectionItem[] }) {\n return (\n <div className=\"connections-dashboard\">\n <h2>Manage Your Connections</h2>\n <div className=\"connections-grid\">\n {connections.map(connection => (\n <ConnectionManagerCard key={connection.userId} connection={connection} />\n ))}\n </div>\n </div>\n );\n}\n\nfunction ConnectionManagerCard({ connection }: { connection: ConnectionItem }) {\n const {\n connectionStatus,\n isLoading,\n sendConnectionRequest,\n acceptConnectionRequest,\n declineConnectionRequest,\n withdrawConnectionRequest,\n disconnectUser\n } = useConnectionManager({ userId: connection.userId });\n\n const [lastAction, setLastAction] = useState<string | null>(null);\n\n const handleQuickAction = async (action: () => Promise<void>, actionName: string) => {\n setLastAction(actionName);\n try {\n await action();\n console.log(`${actionName} completed for ${connection.username}`);\n } catch (error) {\n console.error(`Failed to ${actionName}:`, error);\n } finally {\n setTimeout(() => setLastAction(null), 2000); // Clear after 2 seconds\n }\n };\n\n const getQuickActions = () => {\n if (isLoading) return [{ label: \"Loading...\", disabled: true }];\n\n switch (connectionStatus) {\n case 'none':\n return [{\n label: \"Connect\",\n action: () => handleQuickAction(() => sendConnectionRequest(), \"connected\"),\n className: \"primary\"\n }];\n \n case 'pending-sent':\n return [{\n label: \"Withdraw\",\n action: () => handleQuickAction(withdrawConnectionRequest, \"withdrew\"),\n className: \"secondary\"\n }];\n \n case 'pending-received':\n return [\n {\n label: \"Accept\",\n action: () => handleQuickAction(acceptConnectionRequest, \"accepted\"),\n className: \"primary\"\n },\n {\n label: \"Decline\",\n action: () => handleQuickAction(declineConnectionRequest, \"declined\"),\n className: \"secondary\"\n }\n ];\n \n case 'connected':\n return [{\n label: \"Disconnect\",\n action: () => handleQuickAction(disconnectUser, \"disconnected\"),\n className: \"danger\"\n }];\n \n default:\n return [];\n }\n };\n\n return (\n <div className=\"connection-manager-card\">\n <div className=\"user-info\">\n {connection.avatar && (\n <img src={connection.avatar} alt={`${connection.username} avatar`} />\n )}\n <div className=\"user-details\">\n <h4>{connection.displayName || connection.username}</h4>\n <p>@{connection.username}</p>\n <span className={`status ${connectionStatus}`}>\n {connectionStatus.replace('-', ' ')}\n </span>\n </div>\n </div>\n\n <div className=\"quick-actions\">\n {lastAction ? (\n <div className=\"action-feedback\">\n ✓ {lastAction}\n </div>\n ) : (\n getQuickActions().map((action, index) => (\n <button\n key={index}\n onClick={action.action}\n disabled={action.disabled}\n className={`quick-action-btn ${action.className || ''}`}\n >\n {action.label}\n </button>\n ))\n )}\n </div>\n </div>\n );\n}\n```\n\n## Parameters & Returns\n\n### Parameters\n\nThe hook accepts an object with the following field:\n\n| Parameter | Type | Required | Description |\n|-----------|----------|----------|------------------------------------------------------|\n| `userId` | `string` | Yes | The ID of the user to manage connection status for |\n\n### Returns\n\nThe hook returns an object containing:\n\n| Field | Type | Description |\n|------------------------------|-----------------------------|----------------------------------------------------- |\n| `connectionStatus` | `ConnectionStatus` | Current connection status |\n| `connectionId` | `string \\| null` | ID of the connection (if exists) |\n| `connectionData` | `ConnectionData` | Detailed connection information |\n| `isLoading` | `boolean` | Whether connection status is being loaded |\n| `sendConnectionRequest` | `(message?: string) => Promise<void>` | Send a connection request |\n| `acceptConnectionRequest` | `() => Promise<void>` | Accept a received connection request |\n| `declineConnectionRequest` | `() => Promise<void>` | Decline a received connection request |\n| `withdrawConnectionRequest` | `() => Promise<void>` | Withdraw a sent connection request |\n| `disconnectUser` | `() => Promise<void>` | Disconnect from a connected user |\n| `removeConnectionSmart` | `() => Promise<void>` | Smart removal of any connection state |\n| `refreshConnectionStatus` | `() => Promise<void>` | Manually refresh the connection status |\n\n### Connection Status Types\n\n| Status | Description |\n|---------------------|------------------------------------------------------|\n| `none` | No connection exists |\n| `pending-sent` | You sent a request awaiting response |\n| `pending-received` | You received a request awaiting your response |\n| `connected` | Mutually connected |\n| `declined-sent` | Your request was declined |\n| `declined-received` | You declined their request |\n\n### Connection Data Object\n\n| Field | Type | Description |\n|-----------------|----------|--------------------------------------------------|\n| `connectionId` | `string \\| null` | ID of the connection relationship |\n| `connectedAt` | `string?` | ISO date when connection was established |\n| `requestedAt` | `string?` | ISO date when connection was requested |\n| `createdAt` | `string?` | ISO date when current state was created |\n| `respondedAt` | `string?` | ISO date when request was responded to |\n| `type` | `'sent' \\| 'received'?` | Direction of request |\n\n### Error Handling\n\nAll action methods can throw errors for:\n- Network connection issues\n- Invalid connection states\n- Server-side validation errors\n- Authentication problems\n\n### Automatic Behavior\n\nThe hook automatically:\n- Fetches initial connection status on mount\n- Prevents actions when user ID matches current user\n- Manages optimistic UI updates during operations\n- Handles loading states for both initial load and actions\n- Refreshes status after successful actions\n\n### Use Cases\n\nThis hook is ideal for:\n- User profile connection management\n- Professional networking interfaces\n- Social platform connection features\n- Connection request inbox systems\n- User discovery and networking tools\n\n### Best Practices\n\n1. **Error Handling**: Always wrap actions in try-catch blocks\n2. **User Feedback**: Provide clear feedback for all connection states\n3. **Confirmations**: Ask for confirmation before destructive actions\n4. **Loading States**: Show loading indicators during actions\n5. **Refresh**: Use refreshConnectionStatus after external changes\n\n### Related Hooks\n\n- `useFetchConnectionStatus` - Status checking only\n- `useRequestConnection` - Basic connection request functionality\n- `useAcceptConnection` - Basic accept functionality\n- `useDeclineConnection` - Basic decline functionality\n- `useRemoveConnection` - Basic removal functionality\n- `useFollowManager` - Similar management hook for follows"