diff --git a/README.md b/README.md index b3741219..eeb99a8c 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,34 @@ ReactDOM.render(, document.getElementById('root')); // React 16-17 createRoot(document.getElementById('root')).render(); // React 18 ``` -### Props +## ❗️❗️ React 18 Strict Mode ❗️❗️ -#### children +React 18 introduced [new behavior](https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state) in Strict Mode whereby it mimics a possible future behavior where React might optimize an app's performance by arbitrarily unmounting components that aren't in use and later remount them (with previous, reused state -- that's the key) when the user needs them again. What constitutes "not in use" and "needs them again" is as yet undefined. + +It should be noted this violates stated behavior about [componentWillUnmount](https://reactjs.org/docs/react-component.html#componentwillunmount): "Once a component instance is unmounted, __it will never be mounted again.__" Not so in Strict Mode! + +Nonetheless, many of you like to use Strict Mode and may not realize what changed in React 18, so we have made our __[best attempt](https://github.com/focus-trap/focus-trap-react/pull/721) at supporting it in v9.0.2__ whereby the trap attempts to detect that it has been remounted with previous state: If the `active` prop's value is `true`, and an internal focus trap instance already exists, the focus trap is re-activated on remount in order to reconcile stated expectations. + +> 🚨 In Strict Mode (and so in dev builds only, since this behavior of Strict Mode only affects dev builds), the trap __will be deactivated as soon as it is mounted__, and then reactivated again, almost immediately, because React will immediately unmount and remount the trap as soon as it's rendered. + +Therefore, __do not use options like onActivate, onPostActivate, onDeactivate, or onPostDeactivate to affect component state__. + +
+Explanation and sample anti-pattern to avoid +

+See this discussion for an example sandbox (issue description) where onDeactivate was used to trigger the close of a dialog when the trap was deactivated (e.g. to react to the user clicking outside the trap with focusTrapOptions.clickOutsideDeactivates=true). +

+

+The result can be that (depending on how you render the trap) in Strict Mode, the dialog never appears because it gets closed as soon as the trap renders, since the trap is deactivated as soon as it's unmounted, and so the onDeactivate handler is called, thus hiding the dialog... +

+

+This is intentional: If the trap gets unmounted, it has no idea if it's being unmounted for good or if it's going to be remounted at some future point in time. It also has no idea of knowing how long it will be until it's remounted again. So it must be deactivated as though it's going away for good in order to prevent unintentional behavior and memory leaks (from orphaned document event listeners). +

+
+ +## Props + +### children > ⚠️ The `` component requires a __single__ child, and this child must __forward refs__ onto the element which will ultimately be considered the trap's container. Since React does not provide for a way to forward refs to class-based components, this means the child must be a __functional__ component that uses the `React.forwardRef()` API. > @@ -237,7 +262,7 @@ const root = createRoot(container); root.render(); ``` -#### focusTrapOptions +### focusTrapOptions Type: `Object`, optional @@ -245,7 +270,7 @@ Pass any of the options available in focus-trap's [createOptions](https://github > ⚠️ See notes about __[testing in JSDom](#testing-in-jsdom)__ (e.g. using Jest) if that's what you currently use. -#### active +### active Type: `Boolean`, optional @@ -253,13 +278,13 @@ By default, the `FocusTrap` activates when it mounts. So you activate and deacti See `demo/demo-special-element.js`. -#### paused +### paused Type: `Boolean`, optional If you would like to pause or unpause the focus trap (see [`focus-trap`'s documentation](https://github.com/focus-trap/focus-trap#focustrappause)), toggle this prop. -#### containerElements +### containerElements Type: `Array of HTMLElement`, optional