14 KiB
ReactiveNotes - Dynamic React Components in Obsidian
Transform your Obsidian vault into a reactive computational environment. ReactiveNotes seamlessly integrates React's component ecosystem with Obsidian's powerful note-taking capabilities, enabling dynamic, interactive documents that evolve with your thoughts.
⚠️ Mobile Support: Currently optimized for desktop use. Mobile support is under development.
Why ReactiveNotes?
- Native Integration: Built from the ground up for Obsidian
- Powerful Yet Simple: Write React components directly in your notes
- Dynamic & Interactive: Turn static notes into living documents
- Extensible Foundation: Build your own tools and visualizations
Compatibility
- Obsidian v1.4.0 or higher
- React v18.2.0
- Libraries:
- Recharts v2.10.0
- Lightweight Charts v4.1.0
- d3-force v3.0.0
- lucide-react v0.263.1
- shadcn/ui (latest components)
🚀 Quick Start
Installation
- Open Obsidian Settings → Community Plugins
- Disable Safe Mode if necessary
- Search for "ReactiveNotes"
- Click Install and Enable
For manual installation:
- Download release from GitHub
- Extract to your vault's
.obsidian/pluginsfolder - Enable in Community Plugins settings
Basic Usage
Create a React component in any note:
```react
const Greeting = () => {
const [name, setName] = useState("World");
return <h1>Hello, {name}!</h1>;
};
export default Greeting;
```
Component Requirements:
- Must include a default export
- Must be self-contained in one code block
- Component name should match default export
Component Structure
A typical component structure follows this basic structure:
```react
// Optional: CDN imports at the top
import ExternalLib from 'https://cdnjs.cloudflare.com/...';
// Component definition
const MyComponent = () => {
// 1. Hooks
const [state, setState] = useState(null);
const componentRef = useRef(null);
// 2. Effects & Lifecycle
useEffect(() => {
// Setup code
return () => {
// Cleanup code
};
}, []);
// 3. Helper functions
const handleEvent = () => {
// Event handling logic
};
// 4. Render
return (
<div>
{/* Your JSX here */}
</div>
);
};
// Required: Default export
export default MyComponent;
```
Key Structure Points:
- Imports always at the top
- Component name matches default export
- Hooks before effects
- Helper functions before render
- Single default export at bottom
📚 Built-in Libraries & Components
React Core & Hooks
import React, { useState, useEffect, useRef, useMemo } from react';
UI Components from shadcn/ui
import {
Card, CardHeader, CardTitle, CardContent,
Tabs, TabsList, TabsTrigger,
Button, Dialog, Form
} from '@/components/ui';
Visualization Libraries
//Include Responsive containers
// Recharts for data visualization
import {
LineChart, BarChart, PieChart,
Line, Bar, Pie,
Tooltip, Legend
} from 'recharts';
// Lightweight Charts for financial charts
import { createChart } from 'lightweight-charts';
// D3-force for network graphs
import {
forceSimulation,
forceLink
} from 'd3-force';
Icons
import {
Upload, Activity, AlertCircle,
TrendingUp, TrendingDown
} from 'lucide-react';
💻 Core Features
1. Component Systems
- Write React components directly in notes
- Full TypeScript and React 18 support
- Hot reloading during development
- Error Boundaries and Suspense
- Resource cleanup and lifecycle management
- Custom component registration
- Real-time component updates
- Error reporting
3. Canvas Manipulation
- Direct canvas access for custom drawing
- Real-time canvas updates
- Image processing capabilities
- Custom overlays and annotations
- Interactive drawing tools
5. Performance
- Efficient re-rendering
- Memory leak prevention
- Lazy loading support
2. State Management
// Local state with React
const [local, setLocal] = useState(0);
// Persistent state in note frontmatter
const [stored, setStored] = useStorage('key', defaultValue);
- Persistent storage between sessions
- State synchronization across components
- Frontmatter integration
- Cross-note state management
Note-Specific Data Persistence
- Each note maintains independent state through frontmatter
- Component states persist across sessions
- Data automatically travels with notes when shared
- Multiple instances of same component can have different states
- Perfect for creating reusable interactive templates
Example:
// In Note1.md - counter starts at 0
const Counter = () => {
const [count1, setCount1] = useStorage('counter', 0);
return <button onClick={() => setCount1(count1 + 1)}>Count: {count1}</button>;
};
// In Note2.md - same component, independent state
const Counter = () => {
const [count2, setCount2] = useStorage('counter', 0);
return <button onClick={() => setCount2(count2 + 1)}>Count: {count2}</button>;
};
3. File System Integration
// Read files with encoding
const text = await window.fs.readFile('data.txt', {
encoding: 'utf8'
});
// Read binary files
const binary = await window.fs.readFile('data.xlsx');
4. CDN Library Support
Import additional libraries from cdnjs:
import Chart from 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js';
Requirements:
- HTTPS URLs from cdnjs.cloudflare.com only
- Imports at component top
- Browser-compatible libraries only
5. Theme Integration
const theme = getTheme(); // Returns 'dark' or 'light'
const styles = {
background: theme === 'dark' ? 'var(--background-primary)' : 'white',
color: theme === 'dark' ? 'var(--text-normal)' : 'black'
};
File System Access
// Read files
const csv = await window.fs.readFile('data.csv', { encoding: 'utf8' });
const binary = await window.fs.readFile('data.xlsx');
🎨 Component Examples
Interactive Data Visualization
```react
const DataChart = () => {
const data = [
{ month: 'Jan', value: 100 },
{ month: 'Feb', value: 150 },
{ month: 'Mar', value: 120 }
];
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Line type="monotone" dataKey="value" />
<Tooltip />
</LineChart>
</ResponsiveContainer>
);
};
```
Canvas Manipulation
const DrawingCanvas = () => {
const canvasRef = useRef(null);
useEffect(() => {
const ctx = canvasRef.current.getContext('2d');
ctx.fillStyle = getTheme() === 'dark' ? '#fff' : '#000';
// Your drawing code
return () => {
// Cleanup
};
}, []);
return <canvas ref={canvasRef} className="w-full h-64" />;
};
Persistent State Component
```react
const NoteTracker = () => {
const [notes, setNotes] = useStorage('notes', []);
const addNote = (text) => {
setNotes(prev => [...prev, {
id: Date.now(),
text,
created: new Date()
}]);
};
return (
<Card>
<CardHeader>
<CardTitle>My Notes</CardTitle>
</CardHeader>
<CardContent>
{notes.map(note => (
<div key={note.id}>{note.text}</div>
))}
<Button onClick={() => addNote("New Note")}>
Add Note
</Button>
</CardContent>
</Card>
);
};
```
Network Visualization
```react
const NetworkGraph = () => {
const [nodes] = useState([
{ id: 1, label: 'Node 1' },
{ id: 2, label: 'Node 2' }
]);
const [links] = useState([
{ source: 1, target: 2 }
]);
return (
<ForceGraph
graphData={{ nodes, links }}
nodeAutoColorBy="label"
nodeLabel="label"
/>
);
};
```
🛠️Development Guide
Component Structure
// Basic component template
const MyComponent = () => {
// 1. Hooks and state
const [data, setData] = useState(null);
const componentRef = useRef(null);
// 2. Effects and lifecycle
useEffect(() => {
// Setup code
return () => {
// Cleanup code
};
}, []);
// 3. Event handlers
const handleUpdate = useCallback(() => {
// Handler code
}, []);
// 4. Render
return (
<div>
{/* Component JSX */}
</div>
);
};
export default MyComponent;
Best Practices
-
Component Design
const ResponsiveComponent = () => { return ( <div className="w-full"> <ResponsiveContainer width="100%" height={400}> {/* Content adapts to container */} </ResponsiveContainer> </div> ); }; -
State Management
const DataComponent = () => { // Persistent data const [savedData, setSavedData] = useStorage('data-key', []); // Temporary UI state const [isLoading, setIsLoading] = useState(false); // Computed values const processedData = useMemo(() => { return savedData.map(item => /* process */); }, [savedData]); }; -
Error Handling
const SafeComponent = () => { return ( <ErrorBoundary fallback={({ error }) => ( <div className="text-red-500"> Error: {error.message} </div> )} > {/* Protected content */} </ErrorBoundary> ); }; -
Error Checking:
- Check browser console for errors
- Verify imports are working
- Ensure all required exports are present
-
Performance:
- Keep components focused and minimal
- Use CDN imports sparingly
- Clean up resources in useEffect
-
Testing:
- Test in both light and dark themes
- Verify mobile responsiveness (Hasn't been tested for mobile yet)
- Check CDN imports load correctly
🔧 Troubleshooting
Common Issues
-
Component not rendering
- Verify default export is present
- Check console for React errors
- Ensure all imports are available and supported
- Validate JSX syntax
-
CDN imports not working
- Verify HTTPS URL
- Check library compatibility
- Confirm import syntax
-
State persistence issues
- Check frontmatter permissions
- Verify storage key uniqueness
- Validate data serialization
- Monitor storage size limits
-
Performance Problems
- Use React DevTools for profiling
- Implement proper cleanup in useEffect
- Optimize re-renders with useMemo/useCallback
- Monitor memory usage with large datasets
⚠️ Important Notes
Mobile Compatibility
- Currently optimized for desktop use
- Mobile support is under development
- Test thoroughly on mobile devices
Performance and Resource Management
Best practices for optimal performance:
- Clean up subscriptions and intervals
- Dispose of chart instances
- Release canvas resources
- Free up WebGL contexts
- Use persistent storage thoughtfully
Security Considerations
- CDN imports are restricted to cdnjs.cloudflare.com
- File system access is limited to vault
- Component code runs in sandbox
- No external network requests
📋 Technical Reference
Available APIs
-
File System
// File operations await window.fs.readFile(path, options); const text = await window.fs.readFile(path, { encoding: 'utf8' }); -
Theme Management
// Theme utilities const theme = getTheme(); const isDark = theme === 'dark'; -
Storage
// Storage hooks const [value, setValue] = useStorage(key, defaultValue); const stored = useStorage(key, null, storage);
Limitations
-
Environment Constraints
- CDN imports limited to cdnjs.cloudflare.com
- Components must be self-contained in one code block
- Network requests follow Obsidian's security policies
-
Performance Considerations
- Large datasets may impact performance
- Heavy animations should be optimized
- Memory usage needs monitoring
- Mobile performance varies
-
Work in Progress
- Mobile support still under development
- Some touch interactions need optimization
- Advanced debugging tools coming soon
🔮 Future Plans
Upcoming Features
-
Advanced Visualization
- More chart types
- Enhanced animations
- 3D visualization support
- Custom chart templates
-
Developer Experience
- Component templates
- Debug tools
- Performance monitoring
- Testing utilities
-
Mobile Support
- Touch optimization
- Responsive layouts
- Performance improvements
- Mobile-specific components
🤝 Contributing
We welcome contributions! Here's how to get started:
- Fork the repository
- Create your feature branch
- Make your changes
- Submit a pull request
Please read our Contributing Guide for details.
💬 Support
- Report bugs through GitHub Issues
- Request features in Discussions
- Join our Discord community
License
MIT License - see LICENSE for details
⚡️Built to advance the Obsidian powerhouse
