Files
audio-ui/lib/hooks/useMultiTrack.ts
Sebastian Krüger a2636bb3a9 fix: ensure track names are strings when loading from localStorage
Added string conversion and error handling when loading tracks from localStorage to prevent corrupted data from causing React rendering errors. If localStorage data is corrupted, it will be cleared automatically.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 22:30:45 +01:00

99 lines
2.8 KiB
TypeScript

import { useState, useCallback, useEffect } from 'react';
import type { Track } from '@/types/track';
import { createTrack, createTrackFromBuffer } from '@/lib/audio/track-utils';
const STORAGE_KEY = 'audio-ui-multi-track';
export function useMultiTrack() {
const [tracks, setTracks] = useState<Track[]>(() => {
if (typeof window === 'undefined') return [];
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const parsed = JSON.parse(saved);
// Note: AudioBuffers can't be serialized, so we only restore track metadata
return parsed.map((t: any) => ({
...t,
name: String(t.name || 'Untitled Track'), // Ensure name is always a string
audioBuffer: null, // Will need to be reloaded
}));
}
} catch (error) {
console.error('Failed to load tracks from localStorage:', error);
// Clear corrupted data
localStorage.removeItem(STORAGE_KEY);
}
return [];
});
// Save tracks to localStorage (without audio buffers)
useEffect(() => {
if (typeof window === 'undefined') return;
try {
const trackData = tracks.map(({ audioBuffer, ...track }) => track);
localStorage.setItem(STORAGE_KEY, JSON.stringify(trackData));
} catch (error) {
console.error('Failed to save tracks to localStorage:', error);
}
}, [tracks]);
const addTrack = useCallback((name?: string) => {
const track = createTrack(name);
setTracks((prev) => [...prev, track]);
return track;
}, []);
const addTrackFromBuffer = useCallback((buffer: AudioBuffer, name?: string) => {
const track = createTrackFromBuffer(buffer, name);
setTracks((prev) => [...prev, track]);
return track;
}, []);
const removeTrack = useCallback((trackId: string) => {
setTracks((prev) => prev.filter((t) => t.id !== trackId));
}, []);
const updateTrack = useCallback((trackId: string, updates: Partial<Track>) => {
setTracks((prev) =>
prev.map((track) =>
track.id === trackId ? { ...track, ...updates } : track
)
);
}, []);
const clearTracks = useCallback(() => {
setTracks([]);
}, []);
const reorderTracks = useCallback((fromIndex: number, toIndex: number) => {
setTracks((prev) => {
const newTracks = [...prev];
const [movedTrack] = newTracks.splice(fromIndex, 1);
newTracks.splice(toIndex, 0, movedTrack);
return newTracks;
});
}, []);
const setTrackBuffer = useCallback((trackId: string, buffer: AudioBuffer) => {
setTracks((prev) =>
prev.map((track) =>
track.id === trackId ? { ...track, audioBuffer: buffer } : track
)
);
}, []);
return {
tracks,
addTrack,
addTrackFromBuffer,
removeTrack,
updateTrack,
clearTracks,
reorderTracks,
setTrackBuffer,
};
}