Skip to content

React Integration

Learn how to integrate 0.link into your React applications. This comprehensive guide covers setup, authentication, and implementing core features using modern React patterns.

Quick Start

Installation

bash
npm install @0link/react @0link/sdk
# or
yarn add @0link/react @0link/sdk

Basic Setup

jsx
import React from 'react';
import { ZeroLinkProvider } from '@0link/react';

function App() {
  return (
    <ZeroLinkProvider apiKey={process.env.REACT_APP_ZEROLINK_API_KEY}>
      <YourApp />
    </ZeroLinkProvider>
  );
}

export default App;

Configuration

Environment Variables

Create a .env file in your project root:

bash
REACT_APP_ZEROLINK_API_KEY=0link_sk_your_api_key_here
REACT_APP_ZEROLINK_ENVIRONMENT=development

Provider Configuration

jsx
import { ZeroLinkProvider } from '@0link/react';

function App() {
  return (
    <ZeroLinkProvider
      apiKey={process.env.REACT_APP_ZEROLINK_API_KEY}
      environment={process.env.REACT_APP_ZEROLINK_ENVIRONMENT}
      options={{
        timeout: 10000,
        retryAttempts: 3,
        debug: process.env.NODE_ENV === 'development'
      }}
    >
      <Router>
        <Routes>
          <Route path="/" element={<Dashboard />} />
          <Route path="/projects" element={<Projects />} />
        </Routes>
      </Router>
    </ZeroLinkProvider>
  );
}

Authentication

Check Authentication Status

jsx
import { useZeroLink } from '@0link/react';

function AuthStatus() {
  const { isAuthenticated, user, loading } = useZeroLink();

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {isAuthenticated ? (
        <p>Welcome, {user.name}!</p>
      ) : (
        <p>Please check your API key configuration.</p>
      )}
    </div>
  );
}

Protected Routes

jsx
import { useZeroLink } from '@0link/react';
import { Navigate } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const { isAuthenticated, loading } = useZeroLink();

  if (loading) return <div>Loading...</div>;
  if (!isAuthenticated) return <Navigate to="/unauthorized" />;

  return children;
}

// Usage
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

Working with Projects

List Projects

jsx
import { useProjects } from '@0link/react';

function ProjectsList() {
  const { projects, loading, error, refetch } = useProjects();

  if (loading) return <div>Loading projects...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Projects</h2>
      <button onClick={refetch}>Refresh</button>
      {projects.map(project => (
        <div key={project.id}>
          <h3>{project.name}</h3>
          <p>{project.description}</p>
          <span>Created: {new Date(project.created_at).toLocaleDateString()}</span>
        </div>
      ))}
    </div>
  );
}

Create Project

jsx
import { useState } from 'react';
import { useCreateProject } from '@0link/react';

function CreateProject() {
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const { createProject, loading, error } = useCreateProject();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const project = await createProject({ name, description });
      console.log('Project created:', project);
      setName('');
      setDescription('');
    } catch (err) {
      console.error('Failed to create project:', err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Project Name</label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
      </div>
      <div>
        <label>Description</label>
        <textarea
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        />
      </div>
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Project'}
      </button>
      {error && <div className="error">{error.message}</div>}
    </form>
  );
}

Asset Management

Upload Assets

jsx
import { useState } from 'react';
import { useUploadAsset } from '@0link/react';

function AssetUploader({ projectId }) {
  const [file, setFile] = useState(null);
  const { uploadAsset, loading, progress, error } = useUploadAsset();

  const handleUpload = async () => {
    if (!file) return;

    try {
      const asset = await uploadAsset({
        file,
        projectId,
        name: file.name
      });
      console.log('Asset uploaded:', asset);
      setFile(null);
    } catch (err) {
      console.error('Upload failed:', err);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => setFile(e.target.files[0])}
      />
      <button
        onClick={handleUpload}
        disabled={!file || loading}
      >
        {loading ? `Uploading... ${progress}%` : 'Upload'}
      </button>
      {error && <div className="error">{error.message}</div>}
    </div>
  );
}

Display Assets

jsx
import { useAssets } from '@0link/react';

function AssetGallery({ projectId }) {
  const { assets, loading, error } = useAssets(projectId);

  if (loading) return <div>Loading assets...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div className="asset-gallery">
      {assets.map(asset => (
        <div key={asset.id} className="asset-item">
          {asset.content_type.startsWith('image/') ? (
            <img
              src={asset.url}
              alt={asset.name}
              style={{ maxWidth: 200, maxHeight: 200 }}
            />
          ) : (
            <div className="file-icon">
              📄 {asset.name}
            </div>
          )}
          <p>{asset.name}</p>
          <small>{(asset.size / 1024).toFixed(1)} KB</small>
        </div>
      ))}
    </div>
  );
}

Real-time Updates

WebSocket Integration

jsx
import { useEffect } from 'react';
import { useZeroLinkWebSocket } from '@0link/react';

function RealtimeUpdates() {
  const { subscribe, unsubscribe, isConnected } = useZeroLinkWebSocket();

  useEffect(() => {
    const handleAssetUploaded = (event) => {
      console.log('New asset uploaded:', event.data);
      // Update UI accordingly
    };

    subscribe('asset.uploaded', handleAssetUploaded);

    return () => {
      unsubscribe('asset.uploaded', handleAssetUploaded);
    };
  }, [subscribe, unsubscribe]);

  return (
    <div>
      Status: {isConnected ? '🟢 Connected' : '🔴 Disconnected'}
    </div>
  );
}

Error Handling

Global Error Boundary

jsx
import { Component } from 'react';

class ZeroLinkErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('ZeroLink Error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong with 0.link integration</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try Again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Wrap your app
function App() {
  return (
    <ZeroLinkErrorBoundary>
      <ZeroLinkProvider apiKey={apiKey}>
        <YourApp />
      </ZeroLinkProvider>
    </ZeroLinkErrorBoundary>
  );
}

Hook-level Error Handling

jsx
import { useZeroLinkError } from '@0link/react';

function ComponentWithErrorHandling() {
  const { handleError, clearErrors } = useZeroLinkError();

  const someOperation = async () => {
    try {
      // Your 0.link operation
    } catch (error) {
      handleError(error, {
        context: 'Failed to load projects',
        retryable: true
      });
    }
  };

  return (
    <div>
      {/* Your component */}
      <button onClick={clearErrors}>Clear Errors</button>
    </div>
  );
}

Performance Optimization

Memoization

jsx
import { useMemo } from 'react';
import { useProjects } from '@0link/react';

function OptimizedProjectsList() {
  const { projects, loading } = useProjects();

  const sortedProjects = useMemo(() => {
    return projects?.sort((a, b) =>
      new Date(b.created_at) - new Date(a.created_at)
    ) || [];
  }, [projects]);

  const projectStats = useMemo(() => {
    return {
      total: sortedProjects.length,
      active: sortedProjects.filter(p => p.status === 'active').length,
      recent: sortedProjects.filter(p =>
        Date.now() - new Date(p.created_at).getTime() < 7 * 24 * 60 * 60 * 1000
      ).length
    };
  }, [sortedProjects]);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <div className="stats">
        <span>Total: {projectStats.total}</span>
        <span>Active: {projectStats.active}</span>
        <span>Recent: {projectStats.recent}</span>
      </div>
      {/* Render projects */}
    </div>
  );
}

Pagination

jsx
import { useState } from 'react';
import { useProjects } from '@0link/react';

function PaginatedProjects() {
  const [cursor, setCursor] = useState(null);
  const { projects, loading, pagination } = useProjects({
    limit: 10,
    cursor
  });

  return (
    <div>
      {/* Render projects */}

      <div className="pagination">
        <button
          onClick={() => setCursor(pagination.previous_cursor)}
          disabled={!pagination.has_previous}
        >
          Previous
        </button>
        <button
          onClick={() => setCursor(pagination.next_cursor)}
          disabled={!pagination.has_more}
        >
          Next
        </button>
      </div>
    </div>
  );
}

Testing

Mock Provider for Testing

jsx
// test-utils.jsx
import { ZeroLinkProvider } from '@0link/react';

export function MockZeroLinkProvider({ children, mockData = {} }) {
  return (
    <ZeroLinkProvider
      apiKey="test_key"
      environment="test"
      mockMode={true}
      mockData={mockData}
    >
      {children}
    </ZeroLinkProvider>
  );
}

Component Testing

jsx
// ProjectsList.test.jsx
import { render, screen } from '@testing-library/react';
import { MockZeroLinkProvider } from './test-utils';
import ProjectsList from './ProjectsList';

test('renders projects list', () => {
  const mockProjects = [
    { id: '1', name: 'Test Project', description: 'A test project' }
  ];

  render(
    <MockZeroLinkProvider mockData={{ projects: mockProjects }}>
      <ProjectsList />
    </MockZeroLinkProvider>
  );

  expect(screen.getByText('Test Project')).toBeInTheDocument();
});

Production Deployment

Environment Configuration

bash
# Production .env
REACT_APP_ZEROLINK_API_KEY=0link_sk_production_key_here
REACT_APP_ZEROLINK_ENVIRONMENT=production

# Development .env.local
REACT_APP_ZEROLINK_API_KEY=0link_sk_development_key_here
REACT_APP_ZEROLINK_ENVIRONMENT=development

Build Optimization

javascript
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        zerolink: {
          test: /[\\/]node_modules[\\/]@0link/,
          name: 'zerolink',
          chunks: 'all',
        },
      },
    },
  },
};

Complete Example

Here's a complete React component that demonstrates multiple 0.link features:

jsx
import React, { useState } from 'react';
import {
  useZeroLink,
  useProjects,
  useCreateProject,
  useAssets,
  useUploadAsset
} from '@0link/react';

function Dashboard() {
  const { user, isAuthenticated } = useZeroLink();
  const { projects, loading: projectsLoading } = useProjects();
  const { createProject, loading: creating } = useCreateProject();
  const [selectedProject, setSelectedProject] = useState(null);
  const { assets } = useAssets(selectedProject?.id);
  const { uploadAsset, loading: uploading } = useUploadAsset();

  if (!isAuthenticated) {
    return <div>Please configure your API key</div>;
  }

  return (
    <div className="dashboard">
      <header>
        <h1>Welcome, {user.name}</h1>
      </header>

      <section>
        <h2>Projects</h2>
        {projectsLoading ? (
          <div>Loading projects...</div>
        ) : (
          <div>
            {projects.map(project => (
              <div
                key={project.id}
                onClick={() => setSelectedProject(project)}
                className={selectedProject?.id === project.id ? 'selected' : ''}
              >
                <h3>{project.name}</h3>
                <p>{project.description}</p>
              </div>
            ))}
          </div>
        )}
      </section>

      {selectedProject && (
        <section>
          <h2>Assets for {selectedProject.name}</h2>
          <input
            type="file"
            onChange={(e) => {
              const file = e.target.files[0];
              if (file) {
                uploadAsset({
                  file,
                  projectId: selectedProject.id,
                  name: file.name
                });
              }
            }}
          />
          {uploading && <div>Uploading...</div>}

          <div className="assets-grid">
            {assets?.map(asset => (
              <div key={asset.id}>
                <img src={asset.url} alt={asset.name} />
                <p>{asset.name}</p>
              </div>
            ))}
          </div>
        </section>
      )}
    </div>
  );
}

export default Dashboard;

Next Steps