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>
  );
}
\cycle("|4|2|2|") Sw: S R G M P D N S. Sh: sa ri ga ma pa dha ni sa

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>
  );
}
\cycle("|4|2|2|") \beatDuration(4) \line("Pallavi") Sw: S , R , G , M , P , D , N , S. , Sh: ni , nnu , ko , ri , va , ra , nam , ma ,

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;
}

See Also