npm package: @siran/chat-react-hooks
Description: Hooks et contextes React réutilisables pour intégrer le système de chat dans des applications React.
Latest version: 0.1.9

# @siran/chat-react-hooks

Hooks et contextes React réutilisables pour intégrer le système de chat dans des applications React.

## Installation

Ce package fait partie du monorepo. Les dépendances sont gérées automatiquement.

## Utilisation

### Configuration initiale

Enveloppez votre application avec le `ChatProvider` :

```tsx
import { ChatProvider } from "@siran/chat-react-hooks";
import { createChatClient } from "@siran/chat-client";

function App() {
  const chatClient = createChatClient({
    engine,
    eventSubscriber,
  });

  return (
    <ChatProvider client={chatClient}>
      <YourApp />
    </ChatProvider>
  );
}
```

### Hooks de données

#### `useMessage(messageId)`

Récupère un message spécifique par son ID depuis le store.

```tsx
import { useMessage } from "@siran/chat-react-hooks";

function MessageView({ messageId }: { messageId: string }) {
  const message = useMessage(messageId);
  
  if (!message) {
    return <div>Message non trouvé</div>;
  }
  
  return <div>{message.content}</div>;
}
```

#### `useConversations(input?, options?)`

Charge et suit les conversations. Modèle: **subscribe → engine pushes updates**

- Charge les conversations une fois au montage (si `autoLoad` est true)
- Les mises à jour arrivent automatiquement via les événements temps réel du store
- Pas de polling, tout passe par les événements temps réel

**Important**: Utilisez `useSubscribeToUser()` pour recevoir les mises à jour en temps réel.

```tsx
import { useConversations, useSubscribeToUser } from "@siran/chat-react-hooks";

function ConversationsList({ userId }: { userId: string }) {
  // S'abonner aux événements de l'utilisateur pour recevoir les mises à jour
  useSubscribeToUser(userId);
  
  const { conversations, loading, error, refetch } = useConversations(
    { userId, status: "active" },
    { autoLoad: true }
  );

  if (loading) return <div>Chargement...</div>;
  if (error) return <div>Erreur: {error.message}</div>;

  return (
    <ul>
      {conversations.map((conv) => (
        <li key={conv.id}>{conv.name || conv.id}</li>
      ))}
    </ul>
  );
}
```

#### `useMessages(conversationId, userId, input?, options?)`

Charge et suit les messages d'une conversation. Modèle: **subscribe → engine pushes updates**

- Charge les messages une fois au montage (si `autoLoad` est true)
- Les mises à jour arrivent automatiquement via les événements temps réel du store
- Pas de polling, tout passe par les événements temps réel

**Important**: Utilisez `useSubscribeToConversation()` pour recevoir les mises à jour en temps réel.

```tsx
import { useMessages, useSubscribeToConversation } from "@siran/chat-react-hooks";

function MessagesList({ conversationId, userId }: { conversationId: string; userId: string }) {
  // S'abonner aux événements de la conversation pour recevoir les mises à jour
  useSubscribeToConversation(conversationId);
  
  const { messages, loading, error, refetch } = useMessages(
    conversationId,
    userId,
    { options: { limit: 50 } },
    { autoLoad: true }
  );

  if (loading) return <div>Chargement...</div>;
  if (error) return <div>Erreur: {error.message}</div>;

  return (
    <ul>
      {messages.map((msg) => (
        <li key={msg.id}>{msg.content}</li>
      ))}
    </ul>
  );
}
```

#### `useTyping(conversationId)`

Obtient les utilisateurs en train de taper dans une conversation.

```tsx
import { useTyping } from "@siran/chat-react-hooks";

function TypingIndicator({ conversationId }: { conversationId: string }) {
  const typingUsers = useTyping(conversationId);

  if (typingUsers.length === 0) return null;

  return (
    <div>
      {typingUsers.length === 1
        ? `Utilisateur ${typingUsers[0]} est en train de taper...`
        : `${typingUsers.length} utilisateurs sont en train de taper...`}
    </div>
  );
}
```

#### `usePresence(userId)`

Obtient la présence d'un utilisateur.

```tsx
import { usePresence } from "@siran/chat-react-hooks";

function UserStatus({ userId }: { userId: string }) {
  const presence = usePresence(userId);

  if (!presence) return <span>Statut inconnu</span>;

  return (
    <div>
      <span>Statut: {presence.status}</span>
      {presence.lastSeenAt && (
        <span>Vu pour la dernière fois: {presence.lastSeenAt.toLocaleString()}</span>
      )}
    </div>
  );
}
```

### Hooks de mutations

#### `useSendMessage()`

Envoie un message dans une conversation.

```tsx
import { useSendMessage } from "@siran/chat-react-hooks";

function MessageInput({ conversationId, senderId }: Props) {
  const [content, setContent] = useState("");
  const { sendMessage, loading, error } = useSendMessage();

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();

    const result = await sendMessage({
      conversationId,
      senderId,
      content,
    });

    if (result.ok) {
      setContent("");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={content}
        onChange={(e) => setContent(e.target.value)}
        disabled={loading}
      />
      <button type="submit" disabled={loading}>
        {loading ? "Envoi..." : "Envoyer"}
      </button>
      {error && <div>Erreur: {error.message}</div>}
    </form>
  );
}
```

#### `useCreateConversation()`

Crée une nouvelle conversation.

```tsx
import { useCreateConversation } from "@siran/chat-react-hooks";

function CreateConversationButton({ participantIds }: Props) {
  const { createConversation, loading, error } = useCreateConversation();

  const handleCreate = async () => {
    const result = await createConversation({
      type: "direct",
      participantIds,
      createdBy: "user-123",
    });

    if (result.ok) {
      console.log("Conversation créée:", result.conversation.id);
    }
  };

  return (
    <button onClick={handleCreate} disabled={loading}>
      {loading ? "Création..." : "Créer une conversation"}
    </button>
  );
}
```

#### `useEditMessage()`

Édite un message existant.

```tsx
import { useEditMessage } from "@siran/chat-react-hooks";

function EditMessageButton({ messageId }: { messageId: string }) {
  const [newContent, setNewContent] = useState("");
  const { editMessage, loading, error } = useEditMessage();

  const handleEdit = async () => {
    const result = await editMessage({
      messageId,
      content: newContent,
    });

    if (result.ok) {
      console.log("Message édité");
    }
  };

  return (
    <div>
      <input
        value={newContent}
        onChange={(e) => setNewContent(e.target.value)}
      />
      <button onClick={handleEdit} disabled={loading}>
        {loading ? "Édition..." : "Éditer"}
      </button>
      {error && <div>Erreur: {error.message}</div>}
    </div>
  );
}
```

#### `useDeleteMessage()`

Supprime un message.

```tsx
import { useDeleteMessage } from "@siran/chat-react-hooks";

function DeleteMessageButton({ messageId }: { messageId: string }) {
  const { deleteMessage, loading, error } = useDeleteMessage();

  const handleDelete = async () => {
    if (confirm("Êtes-vous sûr de vouloir supprimer ce message ?")) {
      const result = await deleteMessage({ messageId });

      if (result.ok) {
        console.log("Message supprimé");
      }
    }
  };

  return (
    <button onClick={handleDelete} disabled={loading}>
      {loading ? "Suppression..." : "Supprimer"}
    </button>
  );
}
```

#### `useMarkMessageAsRead()`

Marque un message comme lu.

```tsx
import { useMarkMessageAsRead } from "@siran/chat-react-hooks";

function MessageItem({ messageId }: { messageId: string }) {
  const { markAsRead, loading } = useMarkMessageAsRead();

  useEffect(() => {
    // Marquer comme lu quand le message devient visible
    markAsRead({ messageId });
  }, [messageId, markAsRead]);

  return <div>Message...</div>;
}
```

### Hooks de subscription

#### `useSubscribeToConversation(conversationId, enabled?)`

S'abonne automatiquement aux événements d'une conversation. Se désabonne automatiquement lors du démontage.

```tsx
import { useSubscribeToConversation } from "@siran/chat-react-hooks";

function ConversationView({ conversationId }: { conversationId: string }) {
  // S'abonner automatiquement aux événements de la conversation
  useSubscribeToConversation(conversationId);

  const messages = useMessages(conversationId);

  return <div>Messages...</div>;
}
```

#### `useSubscribeToUser(userId, enabled?)`

S'abonne automatiquement aux événements d'un utilisateur. Se désabonne automatiquement lors du démontage.

```tsx
import { useSubscribeToUser } from "@siran/chat-react-hooks";

function UserPresence({ userId }: { userId: string }) {
  // S'abonner automatiquement aux événements de l'utilisateur
  useSubscribeToUser(userId);

  const presence = usePresence(userId);

  return <div>Statut: {presence?.status}</div>;
}
```

### Hook de base

#### `useChat()`

Accède directement au ChatClient. Utile pour des cas d'usage avancés.

```tsx
import { useChat } from "@siran/chat-react-hooks";

function AdvancedComponent() {
  const chat = useChat();

  // Utiliser directement les méthodes du ChatClient
  useEffect(() => {
    chat.on("message.received", (event) => {
      console.log("Nouveau message:", event.payload.message);
    });
  }, [chat]);

  return <div>...</div>;
}
```

## Architecture

Ce package fournit une couche React au-dessus du `ChatClient` du package `@siran/chat-client`. Il utilise :

- **React Context** pour fournir le ChatClient à toute l'application
- **useSyncExternalStore** pour une intégration optimale avec le store du ChatClient
- **Hooks personnalisés** pour encapsuler la logique de chargement, mutations et subscriptions

### Modèle de données : Subscribe → Engine Pushes Updates

Le package suit le modèle **subscribe → engine pushes updates** :

1. **Chargement initial** : Les hooks chargent les données une fois au montage (si `autoLoad` est true)
2. **Abonnement** : Utilisez les hooks de subscription (`useSubscribeToConversation`, `useSubscribeToUser`) pour écouter les événements temps réel
3. **Mises à jour automatiques** : Le ChatEngine pousse les mises à jour via les événements temps réel, qui mettent à jour le store
4. **Réactivité React** : Les composants se mettent automatiquement à jour grâce à `useSyncExternalStore`

**Pas de polling** : Toutes les mises à jour passent par les événements temps réel, pas de rechargement périodique.

### Exemple de flux complet

```tsx
function ConversationView({ conversationId, userId }: { conversationId: string; userId: string }) {
  // 1. S'abonner aux événements de la conversation
  useSubscribeToConversation(conversationId);
  
  // 2. Charger les messages (une fois au montage)
  const { messages, loading } = useMessages(conversationId, userId, undefined, { autoLoad: true });
  
  // 3. Les nouveaux messages arrivent automatiquement via les événements temps réel
  // Le store se met à jour, et React re-rend automatiquement le composant
  
  return <div>{/* Messages affichés automatiquement mis à jour */}</div>;
}
```

## Types

Tous les types sont exportés depuis `@siran/chat-core` et `@siran/chat-client`. Consultez la documentation de ces packages pour plus de détails.