Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#773] Add note about single child components #781

Merged
merged 2 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kind-doors-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'focus-trap-react': patch
---

Update README with a note about the `children` prop stating that the trap requires a single child, and that if a component is used, it must be a **functional** component that forwards refs.
98 changes: 95 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ You can read further code examples in `demo/` (it's very simple), and [see how i

Here's one more simple example:

```js
```jsx
const React = require('react');
const ReactDOM = require('react-dom'); // React 16-17
const { createRoot } = require('react-dom/client'); // React 18
Expand Down Expand Up @@ -147,6 +147,96 @@ createRoot(document.getElementById('root')).render(<Demo />); // React 18

### Props

#### children

> ⚠️ The `<FocusTrap>` 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.
>
> If you must use a __class__-based component as the trap's container, then you will need to get your own ref to it upon render, and use the `containerElements` prop (initially set to an empty array `[]`) in order to provide the ref's element to it once updated by React (hint: use a [callback ref](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs)).

> 💬 The child is ignored (but still rendered) if the `containerElements` prop is used to imperatively provide trap container elements.

Example:

```jsx
const React = require('react');
const { createRoot } = require('react-dom/client');
const propTypes = require('prop-types');
const FocusTrap = require('../../dist/focus-trap-react');

const container = document.getElementById('demo-function-child');

const TrapChild = React.forwardRef(function ({ onDeactivate }, ref) {
return (
<div ref={ref}>
<p>
Here is a focus trap <a href="#">with</a> <a href="#">some</a>{' '}
<a href="#">focusable</a> parts.
</p>
<p>
<button
onClick={onDeactivate}
aria-describedby="class-child-heading"
>
deactivate trap
</button>
</p>
</div>
);
});

TrapChild.displayName = 'TrapChild';
TrapChild.propTypes = {
onDeactivate: propTypes.func,
};

class DemoFunctionChild extends React.Component {
constructor(props) {
super(props);

this.state = {
activeTrap: false,
};

this.mountTrap = this.mountTrap.bind(this);
this.unmountTrap = this.unmountTrap.bind(this);
}

mountTrap() {
this.setState({ activeTrap: true });
}

unmountTrap() {
this.setState({ activeTrap: false });
}

render() {
const trap = this.state.activeTrap && (
<FocusTrap
focusTrapOptions={{
onDeactivate: this.unmountTrap,
}}
>
<TrapChild />
</FocusTrap>
);

return (
<div>
<p>
<button onClick={this.mountTrap} aria-describedby="function-child-heading">
activate trap
</button>
</p>
{trap}
</div>
);
}
}

const root = createRoot(container);
root.render(<DemoFunctionChild />);
```

#### focusTrapOptions

Type: `Object`, optional
Expand All @@ -173,9 +263,11 @@ If you would like to pause or unpause the focus trap (see [`focus-trap`'s docume

Type: `Array of HTMLElement`, optional

If passed in, these elements will be used as the boundaries for the focus-trap, __instead of the child__. These get passed as arguments to `focus-trap`'s `updateContainerElements()` method.
If specified, these elements will be used as the boundaries for the focus-trap, __instead of the child__. These get passed as arguments to `focus-trap`'s `updateContainerElements()` method.

> Note that when you use `containerElements`, the need for a child is eliminated as the child is __always__ ignored when the prop is specified, even if the prop is `[]` (an empty array). Also note that if the refs you're putting into the array like `containerElements={[ref1.current, ref2.current]}` and one or both refs aren't resolved yet, resulting in `[null, null]` for example, the trap will not get created. The array must contain at least one `HTMLElement` in order for the trap to get updated.
> 💬 Note that when you use `containerElements`, the need for a child is eliminated as the child is __always__ ignored (though still rendered) when the prop is specified, even if this prop is `[]` (an empty array).
>
> Also note that if the refs you're putting into the array, like `containerElements={[ref1.current, ref2.current]}`, aren't resolved yet, resulting in `[null, null]` for example, the trap will not get created. The array must contain at least one valid `HTMLElement` in order for the trap to get created/updated.

If `containerElements` is subsequently updated (i.e. after the trap has been created) to an empty array (or an array of falsy values like `[null, null]`), the trap will still be active, but the TAB key will do nothing because the trap will not contain any tabbable groups of nodes. At this point, the trap can either be deactivated manually or by unmounting, or an updated set of elements can be given to `containerElements` to resume use of the TAB key.

Expand Down