Summary
Since 9.3.1, Collapse defaults to keepMounted: true, which uses React 19's <Activity mode=\"hidden\"> for collapsed panels. React's Activity in hidden mode suspends all useEffect and useLayoutEffect calls. Libraries that rely on useEffect for setup — most notably react-hook-form — break silently inside collapsed Accordion panels.
Affected versions
- 9.3.1 and above (introduced when
keepMounted defaulted to true with Activity backing)
- Not affected: 9.3.0 (before the fix —
keepMounted was undefined, hitting the else content = children branch with no Activity)
Related issues
Root cause
Collapse.mjs rendering logic uses strict equality:
if (keepMounted === false) content = isExited ? null : children; // unmounts
else if (keepMounted === true) content = <Activity mode="hidden">; // ← broken path
else content = children; // always in DOM
When keepMounted === true (the current default), React 19's Activity is used. Activity in mode="hidden" defers/suspends side effects. react-hook-form registers fields via useEffect inside Controller — those effects never fire in a hidden panel, so fields are never registered.
Reproduction
import { Accordion } from '@mantine/core';
import { useForm, FormProvider, useWatch, Controller } from 'react-hook-form';
function WatchedField({ name }) {
const value = useWatch({ name });
// With shouldUnregister: true (common default), this returns undefined
// when the panel is collapsed — even for values present in defaultValues
console.log(name, value);
return <input {...useFormContext().register(name)} />;
}
export default function Demo() {
const form = useForm({
shouldUnregister: true,
defaultValues: { panels: [{ type: 'A' }] },
});
return (
<FormProvider {...form}>
<Accordion>
<Accordion.Item value="0">
<Accordion.Control>Panel 0</Accordion.Control>
<Accordion.Panel>
{/* useWatch returns undefined here — field never registers via useEffect */}
<WatchedField name="panels.0.type" />
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</FormProvider>
);
}
useWatch({ name: 'panels.0.type' }) returns undefined even though defaultValues has the value, because the Controller's registration useEffect is suspended by Activity.
Observed behaviour
useWatch returns undefined for fields inside a collapsed Accordion.Panel
- Fields are never registered with react-hook-form while the panel is hidden
formState.errors for those fields is also inaccessible
- Affects any library using
useEffect for DOM/subscription setup
Expected behaviour
Fields inside a collapsed-but-mounted panel should behave as if they are mounted normally — effects should run, registrations should complete, subscriptions should be active. This is the contract keepMounted implies.
Current workaround
Override Collapse default props in the Mantine theme to a non-boolean truthy value, which bypasses the Activity branch entirely:
// theme.ts
createTheme({
components: {
Collapse: {
defaultProps: { keepMounted: 1 as unknown as boolean }
}
}
})
1 === true is false (strict equality), so the else content = children branch runs — content stays in DOM, no Activity, effects work normally. This is obviously a hack and not a real solution.
Suggested fix
The proposed keepMountedMode="display-none" from PR #9021 would solve this. Alternatively, the default for keepMounted could remain false (explicit unmount) and users who want DOM preservation without Activity side-effects could opt in to a CSS-based approach.
A simpler short-term fix would be to document that keepMounted={true} suspends effects and to provide a first-class keepMountedMode or similar escape hatch.
Environment
@mantine/core: 9.4.1
- React: 19.x
- react-hook-form: 7.81.0
Summary
Since 9.3.1,
Collapsedefaults tokeepMounted: true, which uses React 19's<Activity mode=\"hidden\">for collapsed panels. React'sActivityin hidden mode suspends alluseEffectanduseLayoutEffectcalls. Libraries that rely onuseEffectfor setup — most notably react-hook-form — break silently inside collapsedAccordionpanels.Affected versions
keepMounteddefaulted totruewithActivitybacking)keepMountedwasundefined, hitting theelse content = childrenbranch with noActivity)Related issues
keepMountedMode="display-none"(open PR, not merged)Root cause
Collapse.mjsrendering logic uses strict equality:When
keepMounted === true(the current default), React 19'sActivityis used.Activityinmode="hidden"defers/suspends side effects. react-hook-form registers fields viauseEffectinsideController— those effects never fire in a hidden panel, so fields are never registered.Reproduction
useWatch({ name: 'panels.0.type' })returnsundefinedeven thoughdefaultValueshas the value, because theController's registrationuseEffectis suspended byActivity.Observed behaviour
useWatchreturnsundefinedfor fields inside a collapsedAccordion.PanelformState.errorsfor those fields is also inaccessibleuseEffectfor DOM/subscription setupExpected behaviour
Fields inside a collapsed-but-mounted panel should behave as if they are mounted normally — effects should run, registrations should complete, subscriptions should be active. This is the contract
keepMountedimplies.Current workaround
Override
Collapsedefault props in the Mantine theme to a non-boolean truthy value, which bypasses theActivitybranch entirely:1 === trueisfalse(strict equality), so theelse content = childrenbranch runs — content stays in DOM, noActivity, effects work normally. This is obviously a hack and not a real solution.Suggested fix
The proposed
keepMountedMode="display-none"from PR #9021 would solve this. Alternatively, the default forkeepMountedcould remainfalse(explicit unmount) and users who want DOM preservation withoutActivityside-effects could opt in to a CSS-based approach.A simpler short-term fix would be to document that
keepMounted={true}suspends effects and to provide a first-classkeepMountedModeor similar escape hatch.Environment
@mantine/core: 9.4.1