---
title: "Build a Chatbot in 5 Minutes"
description: "Build a full-featured chatbot with the AINative React SDK"
canonical: "https://docs.ainative.studio/docs/examples/chatbot"
last-updated: "2026-10-03T21:47:10.764Z"
---

# Build a Chatbot in 5 Minutes

Source: https://docs.ainative.studio/docs/examples/chatbot

> Build a full-featured chatbot with the AINative React SDK

# Build a Chatbot in 5 Minutes

This tutorial walks through building a chatbot with credit tracking and persistent memory using the AINative React SDK.

## Prerequisites

- Node.js 18+
- An AINative API key ([get one free](/docs/getting-started/quick-start))

## 1. Create a React App

```bash
npx create-react-app my-chatbot --template typescript
cd my-chatbot
npm install @ainative/react-sdk
```

## 2. Set Your API Key

Create a `.env` file in the project root:

```bash
REACT_APP_AINATIVE_KEY=your-api-key-here
```

:::warning
Never commit `.env` files to version control. Add `.env` to your `.gitignore`.
:::

## 3. Set Up the Provider

Replace `src/App.tsx` with:

```tsx
import React from 'react';
import { AINativeProvider } from '@ainative/react-sdk';
import { Chatbot } from './Chatbot';

export default function App() {
  return (
    <AINativeProvider config={{ apiKey: process.env.REACT_APP_AINATIVE_KEY! }}>
      <Chatbot />
    </AINativeProvider>
  );
}
```

The `AINativeProvider` makes the API key available to all hooks in the component tree. It defaults to `https://api.ainative.studio/api/v1` as the base URL.

## 4. Build the Chat Component

Create `src/Chatbot.tsx`:

```tsx
import React, { useState, useRef, useEffect } from 'react';
import { useChat, useCredits } from '@ainative/react-sdk';
import type { Message } from '@ainative/react-sdk';

export function Chatbot() {
  const { messages, sendMessage, isLoading, error, reset } = useChat({
    model: 'meta-llama/llama-3.3-70b-instruct',
    temperature: 0.7,
    onError: (err) => console.error('Chat error:', err),
  });

  const { balance } = useCredits();
  const [input, setInput] = useState('');
  const bottomRef = useRef<HTMLDivElement>(null);

  // Auto-scroll to bottom when new messages arrive
  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  const handleSend = async () => {
    const text = input.trim();
    if (!text || isLoading) return;

    setInput('');

    // Build the full message array with the new user message
    const updatedMessages: Message[] = [
      ...messages,
      { role: 'user', content: text },
    ];

    await sendMessage(updatedMessages);
  };

  return (
    <div style={styles.container}>
      {/* Header with credit balance */}
      <header style={styles.header}>
        <h2 style={{ margin: 0 }}>AI Chatbot</h2>
        {balance && (
          <span style={styles.credits}>
            {balance.remaining_credits.toLocaleString()} credits
          </span>
        )}
      </header>

      {/* Message list */}
      <div style={styles.messageArea}>
        {messages.length === 0 && (
          <p style={styles.placeholder}>
            Send a message to start chatting.
          </p>
        )}

        {messages.map((msg, i) => (
          <div
            key={i}
            style={{
              ...styles.bubble,
              alignSelf: msg.role === 'user' ? 'flex-end' : 'flex-start',
              background: msg.role === 'user' ? '#0066ff' : '#f0f0f0',
              color: msg.role === 'user' ? '#fff' : '#000',
            }}
          >
            {msg.content}
          </div>
        ))}

        {isLoading && (
          <div style={{ ...styles.bubble, background: '#f0f0f0', color: '#999' }}>
            Thinking...
          </div>
        )}

        {error && (
          <div style={{ ...styles.bubble, background: '#fee', color: '#c00' }}>
            Error: {error.message}
          </div>
        )}

        <div ref={bottomRef} />
      </div>

      {/* Input area */}
      <div style={styles.inputArea}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && handleSend()}
          placeholder="Type a message..."
          style={styles.input}
          disabled={isLoading}
        />
        <button onClick={handleSend} disabled={isLoading} style={styles.sendBtn}>
          Send
        </button>
        <button onClick={reset} style={styles.clearBtn}>
          Clear
        </button>
      </div>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  container: {
    maxWidth: 640,
    margin: '40px auto',
    fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif',
    display: 'flex',
    flexDirection: 'column',
    height: '80vh',
  },
  header: {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: '12px 16px',
    borderBottom: '1px solid #eee',
  },
  credits: {
    fontSize: 14,
    color: '#666',
    background: '#f5f5f5',
    padding: '4px 10px',
    borderRadius: 12,
  },
  messageArea: {
    flex: 1,
    overflowY: 'auto',
    padding: 16,
    display: 'flex',
    flexDirection: 'column',
    gap: 8,
  },
  placeholder: {
    textAlign: 'center',
    color: '#999',
    marginTop: 100,
  },
  bubble: {
    maxWidth: '80%',
    padding: '10px 14px',
    borderRadius: 16,
    lineHeight: 1.5,
    whiteSpace: 'pre-wrap',
  },
  inputArea: {
    display: 'flex',
    gap: 8,
    padding: 16,
    borderTop: '1px solid #eee',
  },
  input: {
    flex: 1,
    padding: '10px 14px',
    borderRadius: 8,
    border: '1px solid #ddd',
    fontSize: 15,
    outline: 'none',
  },
  sendBtn: {
    padding: '10px 20px',
    borderRadius: 8,
    border: 'none',
    background: '#0066ff',
    color: '#fff',
    cursor: 'pointer',
    fontSize: 15,
  },
  clearBtn: {
    padding: '10px 14px',
    borderRadius: 8,
    border: '1px solid #ddd',
    background: '#fff',
    cursor: 'pointer',
    fontSize: 15,
  },
};
```

## 5. Run It

```bash
npm start
```

Open [http://localhost:3000](http://localhost:3000). You have a working chatbot with:

- Chat completions via Llama 3.3 70B
- Credit balance display
- Auto-scrolling message list
- Loading and error states

## 6. Add Memory (Optional)

Want the chatbot to remember things across sessions? Add the `useMemory` hook:

```tsx
import { useChat, useCredits, useMemory } from '@ainative/react-sdk';

export function ChatbotWithMemory() {
  const { messages, sendMessage, isLoading, reset } = useChat({
    model: 'meta-llama/llama-3.3-70b-instruct',
  });
  const { balance } = useCredits();
  const { remember, recall } = useMemory('chatbot-user');
  const [input, setInput] = useState('');

  const handleSend = async () => {
    const text = input.trim();
    if (!text || isLoading) return;
    setInput('');

    // Check if the user is asking about something we remember
    const relatedMemories = await recall(text, { limit: 3 });

    // Build context from memories
    const memoryContext = relatedMemories.length > 0
      ? `\n\nRelevant context from previous conversations:\n${relatedMemories.map(m => `- ${m.content}`).join('\n')}`
      : '';

    const systemMessage = {
      role: 'system' as const,
      content: `You are a helpful assistant.${memoryContext}`,
    };

    const response = await sendMessage([
      systemMessage,
      ...messages,
      { role: 'user', content: text },
    ]);

    // Remember important parts of the conversation
    if (response) {
      await remember(`User asked: ${text}`, {
        tags: ['conversation'],
        importance: 0.6,
      });
    }
  };

  // ... render same UI as before
}
```

## How It Works

1. **`AINativeProvider`** sets up a React context with your API key.
2. **`useChat`** manages the message array. When you call `sendMessage(messages)`, it POSTs to `/public/managed-chat/chat/completions` and appends the assistant reply.
3. **`useCredits`** fetches your balance from `/public/credits/balance` on mount.
4. **`useMemory`** stores and recalls facts via the ZeroMemory API (`/public/memory/v2/remember` and `/public/memory/v2/recall`).

## Error Handling

The `useChat` hook provides an `error` field and an `onError` callback. Common status codes:

| Status | Meaning | What to Do |
|--------|---------|------------|
| `401` | Auth expired | Redirect to login or refresh token |
| `429` | Rate limited | Wait and retry (see `Retry-After` header) |
| `503` | Service unavailable | Try a different model |

```tsx
const { error } = useChat({
  onError: (err) => {
    if (err.status === 429) {
      showToast('Rate limited. Waiting...');
    }
  },
});
```

## Next Steps

- [React SDK Reference](/docs/sdks/react) -- Full hook API docs
- [Next.js SDK](/docs/sdks/nextjs) -- Add server-side auth and API routes
- [ZeroMemory](/docs/zeromemory/overview) -- Deep dive into persistent memory
- [RAG Pipeline](/docs/examples/rag-pipeline) -- Build a document Q&A system
