| assets | ||
| src | ||
| .editorconfig | ||
| .env.example | ||
| .eslintignore | ||
| .eslintrc | ||
| .gitignore | ||
| .npmrc | ||
| COMPONENTS.md | ||
| CONTRIBUTING.md | ||
| esbuild.config.mjs | ||
| main.tsx | ||
| manifest.json | ||
| outStyles.css | ||
| package-lock.json | ||
| package.json | ||
| react-shim.js | ||
| README.md | ||
| styles.css | ||
| tsconfig.json | ||
| version-bump.mjs | ||
| versions.json | ||
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
🚀 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 ro Render must be the first component within codeblock
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
📚 Available Libraries & Components
ReactiveNotes provides access to popular libraries and components directly in your notes:
Core Libraries
// React and hooks
import React, { useState, useEffect, useRef, useMemo } from 'react';
// Data visualization
import { LineChart, BarChart, PieChart } from 'recharts';
import { createChart } from 'lightweight-charts';
// Data processing
import Papa from 'papaparse'; // CSV parsing
import * as XLSX from 'xlsx'; // Excel files
import * as mathjs from 'mathjs'; // Mathematics
import { format, parseISO } from 'date-fns'; // Date handling
// Interface components
import { Card, Tabs, Switch } from '@/components/ui';
import { TrendingUp, Activity, Settings } from 'lucide-react'; // 70+ icons
import * as LucideIcons from 'lucide-react'; // For complete access just use LucideIcons.IconName
See the full component reference for a complete list of available libraries, components and utilities. Even if needed libraries are not provided within this list CDN imports are always an option.
Available in Component Scope
All components have access to these objects and functions:
// Note context
noteContext.frontmatter // All frontmatter properties (not just react_data)
noteContext.path // Full path to current note
noteContext.basename // Note filename without extension
// Utilities
getTheme() // Returns 'dark' or 'light'
readFile(path, exts) // File reading utility
// Update frontmatter properties, either in react_data (extProp:false) or at root level(extProp:true) Defaults to react_data
updateFrontmatter(key, value, notePath?, extProp?)
// If properties are null returns entire frontmatter or current note if path not provided, extProp defaults to true: entire frontmatter
getFrontmatter(key?,defaultValue?,notePath?, extProp?)
vaultImport(path, index) // Preprocessor directive to include code from other notes (not a runtime function)
Notice(message, timeout?) // Display non-blocking notifications (use instead of alert() which blocks the UI thread)
💻 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
2. Canvas Manipulation
- HTML Canvas API support for custom drawing
- THREE.js integration for 3D graphics
- Lightweight Charts for financial visualization
- D3 force layout integration for network graphs
- Support for both 2D and 3D rendering contexts
3. Performance
- Efficient re-rendering
- Proper cleanup of resources in useEffect hooks
- Lazy loading support
4. State Management
// Local state with React
const [local, setLocal] = useState(0);
useStorage(
key, // Property name to store in frontmatter
defaultValue, // Default value if property doesn't exist
notePath = null, // Optional: Path to another note (null = current note)
extProp = false // Optional: true = store at root level, false = store in react_data
)
// Persistent state in current note's frontmatter
const [stored, setStored] = useStorage('key', defaultValue);
// Store at root level of frontmatter
const [status, setStatus] = useStorage('status', 'draft', null, true);
// Cross-note + root level: Store at root level of another note
const [tags, setTags] = useStorage('tags', ['react'], 'Template.md', true);
// Set value to undefined to delete keys
// Direct frontmatter manipulation (alternative to useStorage hook)
await updateFrontmatter('react_key', newValue); // Updates react_data.react_key
await updateFrontmatter('custom_key', newValue, null, true); // Updates root level custom_key
await updateFrontmatter('tags', ['react', 'component'], 'path/to/other.md', true); // Updates another note's tags
- Persistent storage between sessions
- State synchronization across components
- Frontmatter integration
- Cross-note state management
- Support for both structured data in react_data and direct frontmatter properties
Note-Specific Data Persistence
- Each note maintains independent state through frontmatter storage under a key called react_data
- 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>;
};
Browser LocalStorage
For components that need to share state across notes or maintain state independent of specific notes, browser localStorage is available.
// Create a localStorage hook
const useLocalStorage = (key, defaultValue) => {
const [state, setState] = useState(() => {
try {
const storedValue = localStorage.getItem(key);
return storedValue ? JSON.parse(storedValue) : defaultValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return defaultValue;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState];
};
// Use it in components
const [value, setValue] = useLocalStorage('shared-key', defaultValue);
//This key is shared across all notes
Key features:
- Data persists across browser sessions
- Shared state across all notes
- Independent of note content
- Approximately 5-10MB storage per domain
- Persists until browser data is cleared
5. File Reading Utility
The plugin provides a flexible file reading utility through the readFile function that helps you interactively select and read files from your vault.
const fileData = await readFile( path,extensions);
Parameters
path(optional): Direct path to a specific file. If provided, skips the file selector. Default:nullextensions(optional): Array of file extensions to filter by. Default:['txt', 'md', 'json', 'csv']
Return Value
Returns a Promise that resolves to an object containing:
path: Full file pathname: File name without extensionextension: File extensioncontent: String content of the file
Returns null if the operation is canceled or fails, allowing you to handle this case gracefully.
// Interactive file selection with file type filtering
const textFile = await readFile(null,['txt', 'md']);
if (textFile) {
console.log(`Selected: ${textFile.name}`);
console.log(`Content: ${textFile.content}`);
}
// Direct file access by path (no modal)
const jsonFile = await readFile('path/to/config.json');
if (jsonFile) {
const config = JSON.parse(jsonFile.content);
}
// Read CSV files
const csvFile = await readFile(null,['csv']);
if (csvFile) {
// Process CSV content
const lines = csvFile.content.split('\n');
const headers = lines[0].split(',');
// ...
}
// Read binary files like Excel spreadsheets
const excelFile = await readFile(null,['xlsx', 'xls']);
if (excelFile) {
// excelFile.content contains the file data
// Process with SheetJS or other libraries
}
This utility is particularly useful for components that need to process files from your vault, such as data visualizations, document analysis, and custom importers.
6. 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
7. Theme Integration
const theme = getTheme(); // Returns 'dark' or 'light' correspodning to obsidian theme
const styles = {
background: theme === 'dark' ? 'var(--background-primary)' : 'white',
color: theme === 'dark' ? 'var(--text-normal)' : 'black'
};
// Or use 'dark:' within your components
8. Vault Code Imports
Import React code from other notes in your vault:
// Import code from another note
vaultImport('Components/SharedComponents.md', 0); // 0 = first code block
vaultImport('Hooks/CustomHooks.md', 1); // 1 = second code block
const MyComponent = () => {
// Use imported components and hooks from these files
const data = useImportedHook();
return <ImportedComponent data={data} />;
};
9. Definition Storage Blocks
Not all react code blocks need to be valid React you can use React code blocks to store and organize utility functions, hooks, and other definitions:
// This displays as a definition storage block instead of rendering
const utilities = () => ({
formatDate: (date) => date.toLocaleDateString(),
calculateSum: (arr) => arr.reduce((a, b) => a + b, 0),
parseData: (raw) => JSON.parse(raw)
});
export default utilities;
When a code block returns an object instead of JSX, it automatically displays as a storage block showing all exported definitions. If you have multiple objects containing definitions only the first object will be previewed.
Perfect for:
Utility function collections Custom hook libraries Configuration objects Shared constants
Import these definitions into other components using vaultImport().
🎨 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"
/>
);
};
```
Other Demonstrated Examples
Simulator (Use within reason for performance)

Progression Dashboard
Knowledge progression dashboard using persistant storage and ability to make new notes/categories

Integrated use case with MCP
MCP fetches data from youtube and uses Reactive Notes to display react component for visualising fetched data.
Games utilising event listeners for key action mapping

🛠️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 let file = await readFile(path, extensions); let content=file.content; -
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






