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"