DEV Community

Apollo
Apollo

Posted on

How to integrate DeepSeek R1 into your React app

Integrating DeepSeek R1 into Your React Application: A Comprehensive Guide

DeepSeek R1 represents a cutting-edge AI model for semantic search and retrieval-augmented generation (RAG) applications. This tutorial will walk you through the complete integration process with React, covering both basic implementation and advanced optimization techniques.

Prerequisites

Before beginning, ensure you have:

  • Node.js v18+ installed
  • React 18+ project
  • DeepSeek API credentials (available from their developer portal)
  • Basic understanding of async/await and React hooks

Installation and Setup

First, install the required dependencies:

npm install @deepseekai/r1-client axios react-query
Enter fullscreen mode Exit fullscreen mode

Create a configuration file for your DeepSeek credentials:

// config/deepseek.js
export const DEEPSEEK_CONFIG = {
  apiKey: process.env.REACT_APP_DEEPSEEK_API_KEY,
  baseUrl: 'https://api.deepseek.ai/v1',
  defaultModel: 'r1-base',
  embeddingModel: 'r1-embedding'
};
Enter fullscreen mode Exit fullscreen mode

Creating the DeepSeek Service Layer

Implement a service class to handle all API interactions:

// services/DeepSeekService.js
import axios from 'axios';
import { DEEPSEEK_CONFIG } from '../config/deepseek';

class DeepSeekService {
  constructor() {
    this.client = axios.create({
      baseURL: DEEPSEEK_CONFIG.baseUrl,
      headers: {
        'Authorization': `Bearer ${DEEPSEEK_CONFIG.apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async query(prompt, context = null, options = {}) {
    const payload = {
      model: options.model || DEEPSEEK_CONFIG.defaultModel,
      prompt,
      context,
      temperature: options.temperature || 0.7,
      max_tokens: options.max_tokens || 150
    };

    try {
      const response = await this.client.post('/query', payload);
      return response.data;
    } catch (error) {
      console.error('DeepSeek query error:', error);
      throw error;
    }
  }

  async getEmbeddings(texts) {
    const response = await this.client.post('/embeddings', {
      model: DEEPSEEK_CONFIG.embeddingModel,
      inputs: Array.isArray(texts) ? texts : [texts]
    });
    return response.data.embeddings;
  }
}

export default new DeepSeekService();
Enter fullscreen mode Exit fullscreen mode

React Hook for DeepSeek Integration

Create a custom hook to manage DeepSeek interactions:

// hooks/useDeepSeek.js
import { useState, useCallback } from 'react';
import { useQuery } from 'react-query';
import DeepSeekService from '../services/DeepSeekService';

export const useDeepSeek = (initialOptions = {}) => {
  const [queryState, setQueryState] = useState({
    isLoading: false,
    error: null,
    data: null
  });

  const executeQuery = useCallback(async (prompt, context, options) => {
    setQueryState(prev => ({ ...prev, isLoading: true, error: null }));
    try {
      const response = await DeepSeekService.query(
        prompt,
        context,
        { ...initialOptions, ...options }
      );
      setQueryState({
        isLoading: false,
        error: null,
        data: response
      });
      return response;
    } catch (error) {
      setQueryState({
        isLoading: false,
        error: error.message,
        data: null
      });
      throw error;
    }
  }, [initialOptions]);

  const { data: embeddings, isLoading: embeddingsLoading } = useQuery(
    ['embeddings', initialOptions.context],
    () => DeepSeekService.getEmbeddings(initialOptions.context),
    {
      enabled: !!initialOptions.context,
      staleTime: 1000 * 60 * 5 // 5 minutes
    }
  );

  return {
    ...queryState,
    executeQuery,
    embeddings,
    embeddingsLoading
  };
};
Enter fullscreen mode Exit fullscreen mode

Implementing a Search Component

Here's a complete React component that uses our DeepSeek integration:

// components/DeepSeekSearch.jsx
import React, { useState } from 'react';
import { useDeepSeek } from '../hooks/useDeepSeek';

const DeepSeekSearch = () => {
  const [input, setInput] = useState('');
  const [context, setContext] = useState('');
  const { data, isLoading, error, executeQuery, embeddings } = useDeepSeek({
    context: context || null,
    temperature: 0.5
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!input.trim()) return;
    await executeQuery(input);
  };

  return (
    <div className="deepseek-container">
      <form onSubmit={handleSubmit}>
        <div className="context-input">
          <label>Context (optional):</label>
          <textarea
            value={context}
            onChange={(e) => setContext(e.target.value)}
            rows={3}
          />
        </div>

        <div className="search-input">
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="Ask DeepSeek R1..."
            disabled={isLoading}
          />
          <button type="submit" disabled={isLoading}>
            {isLoading ? 'Processing...' : 'Search'}
          </button>
        </div>
      </form>

      {error && <div className="error-message">{error}</div>}

      {data && (
        <div className="response-container">
          <h3>Response:</h3>
          <div className="response-content">
            {data.choices?.[0]?.text || 'No response generated'}
          </div>

          {data.usage && (
            <div className="usage-stats">
              <span>Tokens used: {data.usage.total_tokens}</span>
              <span>Prompt: {data.usage.prompt_tokens}</span>
              <span>Completion: {data.usage.completion_tokens}</span>
            </div>
          )}
        </div>
      )}

      {embeddings && (
        <div className="embeddings-info">
          <h4>Context Embeddings:</h4>
          <pre>{JSON.stringify(embeddings[0]?.slice(0, 5), null, 2)}...</pre>
        </div>
      )}
    </div>
  );
};

export default DeepSeekSearch;
Enter fullscreen mode Exit fullscreen mode

Advanced: Implementing Caching with React Query

Optimize performance by implementing a comprehensive caching strategy:

// hooks/useDeepSeekCache.js
import { useQuery, useMutation, useQueryClient } from 'react-query';
import DeepSeekService from '../services/DeepSeekService';

export const useDeepSeekCache = () => {
  const queryClient = useQueryClient();

  const queryKey = (prompt, context) => [
    'deepseek',
    prompt,
    context ? JSON.stringify(context) : 'no-context'
  ];

  const { mutateAsync: executeQuery, ...mutation } = useMutation(
    ({ prompt, context, options }) => 
      DeepSeekService.query(prompt, context, options),
    {
      onSuccess: (data, variables) => {
        queryClient.setQueryData(
          queryKey(variables.prompt, variables.context),
          data
        );
      }
    }
  );

  const prefetchQuery = (prompt, context) => {
    queryClient.prefetchQuery(
      queryKey(prompt, context),
      () => DeepSeekService.query(prompt, context)
    );
  };

  return {
    executeQuery,
    prefetchQuery,
    ...mutation
  };
};
Enter fullscreen mode Exit fullscreen mode

Error Handling and Retry Logic

Enhance your service layer with robust error handling:

// Updated error handling in DeepSeekService.js
async function withRetry(fn, retries = 3, delay = 1000) {
  try {
    return await fn();
  } catch (error) {
    if (retries <= 0) throw error;
    await new Promise(res => setTimeout(res, delay));
    return withRetry(fn, retries - 1, delay * 2);
  }
}

class DeepSeekService {
  // ... existing code ...

  async query(prompt, context, options = {}) {
    return withRetry(async () => {
      const payload = { /* payload construction */ };
      const response = await this.client.post('/query', payload);

      if (response.status === 429) {
        throw new Error('Rate limit exceeded');
      }

      if (response.data?.error) {
        throw new Error(response.data.error.message);
      }

      return response.data;
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Performance Optimization Techniques

Implement these advanced optimizations:

  1. Debounced Input Handling:
// utils/debounce.js
export const debounce = (fn, delay) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};

// In your component
const debouncedExecute = debounce(executeQuery, 500);
Enter fullscreen mode Exit fullscreen mode
  1. Web Workers for Embeddings:
// workers/embedding.worker.js
self.onmessage = async (e) => {
  const { texts } = e.data;
  const response = await fetch('https://api.deepseek.ai/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${YOUR_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'r1-embedding',
      inputs: texts
    })
  });
  const data = await response.json();
  self.postMessage(data.embeddings);
};
Enter fullscreen mode Exit fullscreen mode
  1. Streaming Responses:
async function* streamQuery(prompt, context, options) {
  const response = await fetch(`${DEEPSEEK_CONFIG.baseUrl}/query/stream`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${DEEPSEEK_CONFIG.apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: options.model || DEEPSEEK_CONFIG.defaultModel,
      prompt,
      context,
      stream: true
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n').filter(line => line.trim());

    for (const line of lines) {
      const message = line.replace(/^data: /, '');
      if (message === '[DONE]') return;

      try {
        const parsed = JSON.parse(message);
        yield parsed;
      } catch (e) {
        console.error('Error parsing stream chunk:', e);
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Security Considerations

  1. Environment Variables:
    Always store your API keys in environment variables and never commit them to version control.

  2. Rate Limiting:
    Implement client-side rate limiting to prevent API abuse:

// services/RateLimiter.js
class RateLimiter {
  constructor(maxRequests, interval) {
    this.queue = [];
    this.maxRequests = maxRequests;
    this.interval = interval;
    this.tokens = maxRequests;
    setInterval(() => {
      this.tokens = Math.min(this.tokens + 1, this.maxRequests);
      this.processQueue();
    }, this.interval);
  }

  async execute(fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject });
      this.processQueue();
    });
  }

  processQueue() {
    if (this.tokens > 0 && this.queue.length > 0) {
      this.tokens--;
      const { fn, resolve, reject } = this.queue.shift();
      Promise.resolve(fn()).then(resolve).catch(reject);
    }
  }
}

// Initialize with 5 requests per second
export const apiRateLimiter = new RateLimiter(5, 1000);
Enter fullscreen mode Exit fullscreen mode

Testing Your Integration

Create comprehensive unit tests using Jest:

// __tests__/DeepSeekService.test.js
import DeepSeekService from '../services/DeepSeekService';
import { DEEPSEEK_CONFIG } from '../config/deepseek';

jest.mock('axios');

describe('DeepSeekService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('successful query', async () => {
    const mockResponse = {
      data: {
        choices: [{ text: 'Test response' }],
        usage: { total_tokens: 10 }
      }
    };
    axios.post.mockResolvedValue(mockResponse);

    const result = await DeepSeekService.query('Test prompt');
    expect(result).toEqual(mockResponse.data);
    expect(axios.post).toHaveBeenCalledWith(
      '/query',
      expect.objectContaining({
        prompt: 'Test prompt',
        model: DEEPSEEK_CONFIG.defaultModel
      })
    );
  });

  test('error handling', async () => {
    axios.post.mockRejectedValue(new Error('API error'));
    await expect(DeepSeekService.query('Test')).rejects.toThrow('API error');
  });
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

This comprehensive guide has walked you through integrating DeepSeek R1 into your React application, covering:

  1. Basic API service setup
  2. React hooks for state management
  3. Complete component implementation
  4. Advanced caching and performance techniques
  5. Robust error handling
  6. Security considerations
  7. Testing strategies

The DeepSeek R1 model offers powerful capabilities for semantic search and generation tasks. By following these patterns, you can build responsive, efficient applications that leverage its full potential while maintaining code quality and performance.

Remember to monitor your API usage and adjust the implementation based on your specific application requirements and user patterns.


🚀 Stop Writing Boilerplate Prompts

If you want to skip the setup and code 10x faster with complete AI architecture patterns, grab my Senior React Developer AI Cookbook ($19). It includes Server Action prompt libraries, UI component generation loops, and hydration debugging strategies.

Browse all 10+ developer products at the Apollo AI Store | Or snipe Solana tokens free via @ApolloSniper_Bot.

Top comments (0)