React Integration
Using the Notations library in React applications
This guide shows how to create React components for rendering Carnatic music notation.
Installation
npm install notations
Basic NotationViewer Component
Create a reusable React component for notation rendering:
Basic NotationViewer Component
// NotationViewer.jsx
import React, { useEffect, useRef } from 'react';
import * as N from 'notations';
function NotationViewer({ source, className }) {
const containerRef = useRef(null);
const viewRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
// Create table element for notation
if (!viewRef.current) {
const table = document.createElement('table');
table.classList.add('notation-table');
containerRef.current.appendChild(table);
viewRef.current = new N.Carnatic.NotationView(table);
}
// Parse and render notation
const [notation, beatLayout, errors] = N.load(source);
if (errors.length === 0) {
viewRef.current.renderNotation(notation, beatLayout);
} else {
console.error('Notation errors:', errors);
}
}, [source]);
return <div ref={containerRef} className={className} />;
}
// Usage
function App() {
const notation = `
\\cycle("|4|2|2|")
Sw: S R G M P D N S.
Sh: sa ri ga ma pa dha ni sa
`;
return (
<div>
<h3>My Composition</h3>
<NotationViewer source={notation} className="my-notation" />
</div>
);
}
Component with Source Display and Editing
A more complete component that shows source code and allows editing:
Editable NotationBlock Component
// NotationBlock.jsx
import React, { useState, useEffect, useRef } from 'react';
import * as N from 'notations';
function NotationBlock({ source, caption, showSource = false }) {
const containerRef = useRef(null);
const viewRef = useRef(null);
const [isEditing, setIsEditing] = useState(false);
const [editedSource, setEditedSource] = useState(source);
const [currentSource, setCurrentSource] = useState(source);
useEffect(() => {
if (!containerRef.current) return;
if (!viewRef.current) {
const table = document.createElement('table');
table.classList.add('notation-table');
containerRef.current.appendChild(table);
viewRef.current = new N.Carnatic.NotationView(table);
}
const [notation, beatLayout, errors] = N.load(currentSource);
if (errors.length === 0) {
viewRef.current.tableElement.innerHTML = '';
viewRef.current.renderNotation(notation, beatLayout);
}
}, [currentSource]);
const handleApply = () => {
setCurrentSource(editedSource);
setIsEditing(false);
};
return (
<div className="notation-block">
{caption && <div className="notation-caption">{caption}</div>}
{showSource && (
<div className="notation-source">
{isEditing ? (
<>
<textarea
value={editedSource}
onChange={(e) => setEditedSource(e.target.value)}
/>
<button onClick={handleApply}>Apply</button>
<button onClick={() => setIsEditing(false)}>Cancel</button>
</>
) : (
<>
<pre><code>{currentSource}</code></pre>
<button onClick={() => setIsEditing(true)}>Edit</button>
</>
)}
</div>
)}
<div ref={containerRef} />
</div>
);
}
TypeScript Version
// NotationViewer.tsx
import React, { useEffect, useRef } from 'react';
import * as N from 'notations';
interface NotationViewerProps {
source: string;
className?: string;
onError?: (errors: N.ParseError[]) => void;
}
const NotationViewer: React.FC<NotationViewerProps> = ({
source,
className,
onError
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<N.Carnatic.NotationView | null>(null);
useEffect(() => {
if (!containerRef.current) return;
if (!viewRef.current) {
const table = document.createElement('table');
table.classList.add('notation-table');
containerRef.current.appendChild(table);
viewRef.current = new N.Carnatic.NotationView(table);
}
const [notation, beatLayout, errors] = N.load(source);
if (errors.length === 0) {
viewRef.current.tableElement.innerHTML = '';
viewRef.current.renderNotation(notation, beatLayout);
} else if (onError) {
onError(errors);
}
}, [source, onError]);
return <div ref={containerRef} className={className} />;
};
export default NotationViewer;
Error Handling
function NotationWithErrors({ source }) {
const [errors, setErrors] = useState([]);
return (
<div>
<NotationViewer
source={source}
onError={setErrors}
/>
{errors.length > 0 && (
<div className="error-display">
<h4>Parsing Errors:</h4>
<ul>
{errors.map((err, i) => (
<li key={i}>
Line {err.line}, Col {err.column}: {err.message}
</li>
))}
</ul>
</div>
)}
</div>
);
}
CSS for React Components
/* NotationViewer.css */
.notation-block {
border: 1px solid #e5e7eb;
border-radius: 8px;
margin: 1rem 0;
overflow: hidden;
}
.notation-source {
background: #f3f4f6;
padding: 1rem;
border-bottom: 1px solid #e5e7eb;
}
.notation-caption {
font-weight: 600;
margin-bottom: 0.5rem;
}
.notation-textarea {
width: 100%;
min-height: 150px;
font-family: monospace;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 4px;
}
.notation-actions {
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
}
.notation-output {
padding: 1rem;
}
.error-display {
background: #fef2f2;
border: 1px solid #fecaca;
padding: 1rem;
border-radius: 4px;
margin-top: 0.5rem;
}
.error-display h4 {
color: #dc2626;
margin: 0 0 0.5rem 0;
}