Back to CourseLesson 6 of 8

React Hooks Deep Dive

RANA provides purpose-built React hooks for AI interactions. This lesson covers all available hooks and best practices for building responsive AI interfaces.

Available Hooks

RANA provides several hooks for different use cases:

  • useChat - Full chat interface with history
  • useCompletion - Single completion requests
  • useAgent - Agent interactions with tools
  • useStream - Low-level streaming control
  • useTokenCount - Real-time token counting

useChat Hook

The most commonly used hook for chat interfaces:

import { useChat } from '@rana/react';

function ChatComponent() {
  const {
    // Message state
    messages,           // Array of messages
    input,              // Current input value
    setInput,           // Update input

    // Actions
    send,               // Send current input
    append,             // Add message without sending
    reload,             // Regenerate last response
    stop,               // Stop streaming

    // Status
    isLoading,          // Request in progress
    error,              // Error object if any

    // Utilities
    setMessages,        // Override all messages
    clearMessages       // Clear conversation
  } = useChat({
    api: '/api/chat',
    initialMessages: [],
    onFinish: (message) => console.log('Done:', message),
    onError: (error) => console.error('Error:', error)
  });

  return (
    <div>
      {messages.map(m => (
        <div key={m.id} className={m.role}>
          {m.content}
        </div>
      ))}

      <form onSubmit={(e) => { e.preventDefault(); send(); }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          disabled={isLoading}
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

useChat Options

const chat = useChat({
  // Required
  api: string,                    // API endpoint

  // Initial state
  initialMessages?: Message[],    // Starting messages
  initialInput?: string,          // Starting input value
  id?: string,                    // Conversation ID

  // Callbacks
  onFinish?: (message: Message) => void,
  onError?: (error: Error) => void,
  onResponse?: (response: Response) => void,

  // Request options
  headers?: Record<string, string>,
  body?: Record<string, any>,     // Extra body params
  credentials?: RequestCredentials,

  // Behavior
  sendExtraMessageFields?: boolean,
  streamMode?: 'text' | 'sse'
});

useCompletion Hook

For single-shot completions without conversation history:

import { useCompletion } from '@rana/react';

function CompletionComponent() {
  const {
    completion,    // Current completion text
    input,
    setInput,
    complete,      // Trigger completion
    isLoading,
    error,
    stop
  } = useCompletion({
    api: '/api/complete'
  });

  return (
    <div>
      <textarea
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Enter prompt..."
      />
      <button onClick={() => complete(input)}>
        Generate
      </button>
      {completion && (
        <div className="result">{completion}</div>
      )}
    </div>
  );
}

useAgent Hook

For interactions with agents that use tools:

import { useAgent } from '@rana/react';

function AgentComponent() {
  const {
    messages,
    input,
    setInput,
    send,
    isLoading,
    error,

    // Agent-specific
    toolCalls,           // Current tool calls
    toolResults,         // Results from tools
    isExecutingTool,     // Tool execution in progress
    currentTool          // Currently executing tool name
  } = useAgent({
    api: '/api/agent',
    onToolCall: async (toolCall) => {
      // Handle tool execution client-side if needed
      console.log('Tool called:', toolCall.name);
    },
    onToolResult: (result) => {
      console.log('Tool result:', result);
    }
  });

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <div className={m.role}>{m.content}</div>
          {m.toolCalls?.map(tc => (
            <div key={tc.id} className="tool-call">
              Calling: {tc.name}
            </div>
          ))}
        </div>
      ))}

      {isExecutingTool && (
        <div className="executing">
          Executing {currentTool}...
        </div>
      )}

      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={send}>Send</button>
    </div>
  );
}

useStream Hook

Low-level hook for custom streaming scenarios:

import { useStream } from '@rana/react';

function CustomStreamComponent() {
  const {
    data,           // Accumulated stream data
    chunks,         // Individual chunks
    isStreaming,
    error,
    start,          // Start streaming
    stop,           // Stop streaming
    reset           // Clear data
  } = useStream<string>({
    url: '/api/stream',
    onChunk: (chunk) => {
      console.log('Received chunk:', chunk);
    },
    onComplete: (fullData) => {
      console.log('Stream complete:', fullData);
    }
  });

  return (
    <div>
      <button onClick={() => start({ prompt: 'Hello' })}>
        Start Stream
      </button>
      {isStreaming && <button onClick={stop}>Stop</button>}
      <div>{data}</div>
    </div>
  );
}

useTokenCount Hook

Real-time token counting for input validation:

import { useTokenCount } from '@rana/react';

function TokenAwareInput() {
  const [text, setText] = useState('');
  const { count, isLoading } = useTokenCount(text, {
    model: 'claude-sonnet-4-20250514',
    debounce: 300  // Debounce in ms
  });

  const maxTokens = 4000;
  const isOverLimit = count > maxTokens;

  return (
    <div>
      <textarea
        value={text}
        onChange={(e) => setText(e.target.value)}
        className={isOverLimit ? 'error' : ''}
      />
      <div className="token-count">
        {isLoading ? 'Counting...' : `${count} / ${maxTokens} tokens`}
      </div>
      {isOverLimit && (
        <div className="error">
          Input exceeds token limit
        </div>
      )}
    </div>
  );
}

Combining Hooks

Hooks can be combined for complex interfaces:

function AdvancedChat() {
  const chat = useChat({ api: '/api/chat' });
  const { count } = useTokenCount(chat.input);

  // Calculate total conversation tokens
  const conversationTokens = chat.messages.reduce(
    (acc, m) => acc + (m.tokenCount || 0),
    0
  );

  return (
    <div>
      <div className="stats">
        Conversation: {conversationTokens} tokens
      </div>

      {chat.messages.map(m => (
        <Message key={m.id} message={m} />
      ))}

      <form onSubmit={(e) => { e.preventDefault(); chat.send(); }}>
        <input
          value={chat.input}
          onChange={(e) => chat.setInput(e.target.value)}
        />
        <span>{count} tokens</span>
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

Optimistic Updates

Show user messages immediately for better UX:

const chat = useChat({
  api: '/api/chat',
  // Message appears immediately, before API response
  experimental_prepareRequestBody: ({ messages }) => {
    return { messages };
  }
});

// Messages are optimistically added before send completes
// If send fails, the message is removed automatically

Error Handling Patterns

function ChatWithErrorHandling() {
  const {
    messages,
    input,
    setInput,
    send,
    error,
    reload  // Retry last message
  } = useChat({
    api: '/api/chat',
    onError: (error) => {
      // Log to error tracking service
      trackError(error);
    }
  });

  return (
    <div>
      {messages.map(m => <Message key={m.id} message={m} />)}

      {error && (
        <div className="error-banner">
          <p>Something went wrong: {error.message}</p>
          <button onClick={reload}>Retry</button>
        </div>
      )}

      <form onSubmit={(e) => { e.preventDefault(); send(); }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

Best Practices

  • Use appropriate hooks - useChat for conversations, useCompletion for single requests
  • Handle loading states - Always show loading indicators during API calls
  • Implement error handling - Display errors and provide retry options
  • Debounce token counting - Prevent excessive API calls during typing
  • Clean up on unmount - Hooks handle this automatically, but be aware of in-flight requests

What's Next?

Now that you're comfortable with React hooks, the next lesson covers state management patterns for complex AI applications.