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/sdkBasic 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=developmentProvider 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=developmentBuild 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;