{"record":{"id":"eba11852b36c0ac9","repo":"remotion-dev/remotion","slug":"windowinseconds-cannot-be-changed-dynamically","errorCode":null,"errorMessage":"windowInSeconds cannot be changed dynamically","messagePattern":"windowInSeconds cannot be changed dynamically","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/media-utils/src/use-windowed-audio-data.ts","lineNumber":76,"sourceCode":"}\n\nexport const useWindowedAudioData = ({\n\tsrc,\n\tframe,\n\tfps,\n\twindowInSeconds,\n\tchannelIndex = 0,\n\trequestInit,\n}: UseWindowedAudioDataOptions): UseWindowedAudioDataReturnValue => {\n\tconst isMounted = useRef(true);\n\tconst [audioUtils, setAudioUtils] = useState<AudioUtils | null>(null);\n\tconst [waveFormMap, setWaveformMap] = useState({} as WaveformMap);\n\tconst requests = useRef<Record<string, AbortController | null>>({});\n\tconst [initialWindowInSeconds] = useState(windowInSeconds);\n\tconst [initialRequestInit] = useState(requestInit);\n\n\tif (windowInSeconds !== initialWindowInSeconds) {\n\t\tthrow new Error('windowInSeconds cannot be changed dynamically');\n\t}\n\n\tuseEffect(() => {\n\t\tisMounted.current = true;\n\n\t\treturn () => {\n\t\t\tisMounted.current = false;\n\n\t\t\tObject.values(requests.current).forEach((controller) => {\n\t\t\t\tif (controller) {\n\t\t\t\t\tcontroller.abort();\n\t\t\t\t}\n\t\t\t});\n\t\t\trequests.current = {};\n\n\t\t\tsetWaveformMap({});\n\n\t\t\tif (audioUtils) {","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/media-utils/src/use-windowed-audio-data.ts#L58-L94","documentation":"Thrown synchronously during render by useWindowedAudioData() when windowInSeconds differs from the value captured on first render (line 75-77). The hook freezes the initial windowInSeconds into state because refetching and re-windowing already-loaded waveform data is not supported, so changing it after mount is treated as a programming error.","triggerScenarios":"Passing a windowInSeconds value derived from fps, durationInSeconds, or any state that changes after mount; computing it inline from a prop that updates; passing a different number on re-render due to floating-point or rounding differences.","commonSituations":"Tying windowInSeconds to viewport size or fps that changes responsively; deriving it from a fetched video metadata value that arrives after first paint; passing a value from a slider/slider-like control; two siblings rendering the hook with different window sizes that swap in.","solutions":["Make windowInSeconds a constant or a value that is stable for the lifetime of the component (e.g. a literal or a value memoized with useState initializer).","If you genuinely need a new window size, change the `key` of the component using useWindowedAudioData so React remounts it with the fresh initial value rather than updating the prop.","Compute windowInSeconds outside the render cycle (e.g. from a static config) so it cannot drift between renders.","If the value comes from async metadata, render a placeholder until the metadata resolves and pass the final windowInSeconds as the initial value on first mount."],"exampleFix":"// before (windowInSeconds changes -> throws)\nconst {audioData} = useWindowedAudioData({\n  src,\n  frame,\n  fps,\n  windowInSeconds: duration / 10, // duration arrives later, changes value\n});\n\n// after (remount with key when the intended window changes)\nconst windowInSeconds = duration ? duration / 10 : 1;\nreturn (\n  <Waveform key={windowInSeconds} src={src} windowInSeconds={windowInSeconds} ... />\n);","handlingStrategy":"validation","validationCode":"// Freeze windowInSeconds for the lifetime of the component using useState initializer\nconst [windowInSeconds] = useState(() => computeWindow(fps));\n// or derive it once from props and never change it; remount via key if it must change.","typeGuard":"// Ensures a value is stable across renders via ref comparison\nfunction useStable<T>(value: T): T {\n  const ref = useRef(value);\n  if (ref.current !== value) {\n    // surface the change loudly in dev instead of letting useWindowedAudioData throw\n    throw new Error(\n      `windowInSeconds changed from ${ref.current} to ${value}; remount the component via key instead.`,\n    );\n  }\n  return ref.current;\n}","tryCatchPattern":"// Render-time throw cannot be caught with try/catch in the same component.\n// Wrap the subtree in an ErrorBoundary and remount with a fresh key on intent change:\n// <ErrorBoundary fallback={<Retry/>}>\n//   <Waveform key={windowInSeconds} windowInSeconds={windowInSeconds} ... />\n// </ErrorBoundary>","preventionTips":["Treat windowInSeconds as a constant for the component's lifetime; do not derive it from changing state.","When you need a different window, change the `key` on the component to force a clean remount.","Defer mounting useWindowedAudioData until any async inputs (duration, fps) have settled.","Avoid floating-point drift: round windowInSeconds to a fixed value before passing it in."],"tags":["react-hook","validation","audio","render-time","stale-prop"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}