-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Add experimental docs for Fragment refs #8010
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,9 @@ title: <Fragment> (<>...</>) | |
|
||
<Intro> | ||
|
||
`<Fragment>`, often used via `<>...</>` syntax, lets you group elements without a wrapper node. | ||
`<Fragment>`, often used via `<>...</>` syntax, lets you group elements without a wrapper node. | ||
|
||
<Experimental> Fragments can also accept refs, which enable interacting with underlying DOM nodes without adding wrapper elements. See reference and usage below.</Experimental> | ||
|
||
```js | ||
<> | ||
|
@@ -28,13 +30,42 @@ Wrap elements in `<Fragment>` to group them together in situations where you nee | |
#### Props {/*props*/} | ||
|
||
- **optional** `key`: Fragments declared with the explicit `<Fragment>` syntax may have [keys.](/learn/rendering-lists#keeping-list-items-in-order-with-key) | ||
- <ExperimentalBadge /> **optional** `ref`: A ref object (e.g. from [`useRef`](/reference/react/useRef)) or [callback function](/reference/react-dom/components/common#ref-callback). React provides a `FragmentInstance` as the ref value that implements methods for interacting with the DOM nodes wrapped by the Fragment. | ||
|
||
### <ExperimentalBadge /> FragmentInstance {/*fragmentinstance*/} | ||
|
||
When you pass a ref to a fragment, React provides a `FragmentInstance` object with methods for interacting with the DOM nodes wrapped by the fragment: | ||
|
||
**Event handling methods:** | ||
- `addEventListener(type, listener, options?)`: Adds an event listener to all first-level DOM children of the Fragment. | ||
- `removeEventListener(type, listener, options?)`: Removes an event listener from all first-level DOM children of the Fragment. | ||
- `dispatchEvent(event)`: Dispatches an event to a virtual child of the Fragment to call any added listeners and can bubble to the DOM parent. | ||
|
||
**Layout methods:** | ||
- `compareDocumentPosition(otherNode)`: Compares the document position of the Fragment with another node. | ||
- If the Fragment has children, the native `compareDocumentPosition` value is returned. | ||
- Empty Fragments will attempt to compare positioning within the React tree and include `Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC`. | ||
- Elements that have a different relationship in the React tree and DOM tree due to portaling or other insertions are `Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC`. | ||
- `getClientRects()`: Returns a flat array of `DOMRect` objects representing the bounding rectangles of all children. | ||
- `getRootNode()`: Returns the root node containing the Fragment's parent DOM node. | ||
|
||
**Focus management methods:** | ||
- `focus(options?)`: Focuses the first focusable DOM node in the Fragment. Focus is attempted on nested children depth-first. | ||
- `focusLast(options?)`: Focuses the last focusable DOM node in the Fragment. Focus is attempted on nested children depth-first. | ||
- `blur()`: Removes focus if `document.activeElement` is within the Fragment. | ||
|
||
**Observer methods:** | ||
- `observeUsing(observer)`: Starts observing the Fragment's DOM children with an IntersectionObserver or ResizeObserver. | ||
- `unobserveUsing(observer)`: Stops observing the Fragment's DOM children with the specified observer. | ||
|
||
#### Caveats {/*caveats*/} | ||
|
||
- If you want to pass `key` to a Fragment, you can't use the `<>...</>` syntax. You have to explicitly import `Fragment` from `'react'` and render `<Fragment key={yourKey}>...</Fragment>`. | ||
|
||
- React does not [reset state](/learn/preserving-and-resetting-state) when you go from rendering `<><Child /></>` to `[<Child />]` or back, or when you go from rendering `<><Child /></>` to `<Child />` and back. This only works a single level deep: for example, going from `<><><Child /></></>` to `<Child />` resets the state. See the precise semantics [here.](https://gist.github.com/clemmy/b3ef00f9507909429d8aa0d3ee4f986b) | ||
|
||
- <ExperimentalBadge /> If you want to pass `ref` to a Fragment, you can't use the `<>...</>` syntax. You have to explicitly import `Fragment` from `'react'` and render `<Fragment ref={yourRef}>...</Fragment>`. | ||
|
||
--- | ||
|
||
## Usage {/*usage*/} | ||
|
@@ -208,3 +239,92 @@ function PostBody({ body }) { | |
``` | ||
|
||
</Sandpack> | ||
|
||
--- | ||
|
||
### <ExperimentalBadge /> Using Fragment refs for DOM interaction {/*using-fragment-refs-for-dom-interaction*/} | ||
|
||
Fragment refs allow you to interact with the DOM nodes wrapped by a Fragment without adding extra wrapper elements. This is useful for event handling, visibility tracking, focus management, and replacing deprecated patterns like `ReactDOM.findDOMNode()`. | ||
|
||
```js | ||
import { Fragment } from 'react'; | ||
|
||
function ClickableFragment({ children, onClick }) { | ||
return ( | ||
<Fragment ref={fragmentInstance => { | ||
fragmentInstance.addEventListener('click', handleClick); | ||
return () => fragmentInstance.removeEventListener('click', handleClick); | ||
}}> | ||
{children} | ||
</Fragment> | ||
); | ||
} | ||
``` | ||
--- | ||
|
||
### <ExperimentalBadge /> Tracking visibility with Fragment refs {/*tracking-visibility-with-fragment-refs*/} | ||
|
||
Fragment refs are useful for visibility tracking and intersection observation. This enables you to monitor when content becomes visible without requiring the child Components to expose refs: | ||
|
||
```js {19,21,31-34} | ||
import { Fragment, useRef, useLayoutEffect } from 'react'; | ||
|
||
function VisibilityObserverFragment({ threshold = 0.5, onVisibilityChange, children }) { | ||
const fragmentRef = useRef(null); | ||
|
||
useLayoutEffect(() => { | ||
const observer = new IntersectionObserver( | ||
(entries) => { | ||
onVisibilityChange(entries.some(entry => entry.isIntersecting)) | ||
}, | ||
{ threshold } | ||
); | ||
|
||
fragmentRef.current.observeUsing(observer); | ||
return () => fragmentRef.current.unobserveUsing(observer); | ||
}, [threshold, onVisibilityChange]); | ||
|
||
return ( | ||
<Fragment ref={fragmentRef}> | ||
{children} | ||
</Fragment> | ||
); | ||
} | ||
|
||
function MyComponent() { | ||
const handleVisibilityChange = (isVisible) => { | ||
console.log('Component is', isVisible ? 'visible' : 'hidden'); | ||
}; | ||
|
||
return ( | ||
<VisibilityObserverFragment onVisibilityChange={handleVisibilityChange}> | ||
<SomeThirdPartyComponent /> | ||
<AnotherComponent /> | ||
</VisibilityObserverFragment> | ||
); | ||
} | ||
``` | ||
|
||
This pattern is an alternative to Effect-based visibility logging, which is an anti-pattern in most cases. Relying on Effects alone does not guarantee that the rendered Component is observable by the user. | ||
|
||
--- | ||
|
||
### <ExperimentalBadge /> Focus management with Fragment refs {/*focus-management-with-fragment-refs*/} | ||
|
||
Fragment refs provide focus management methods that work across all DOM nodes within the Fragment: | ||
|
||
```js | ||
import { Fragment, useRef } from 'react'; | ||
|
||
function FocusFragment({ children }) { | ||
const fragmentRef = useRef(null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like a typo, PR #8056 |
||
|
||
return ( | ||
<Fragment ref={(fragmentInstance) => fragmentInstance?.focus()}> | ||
{children} | ||
</Fragment> | ||
); | ||
} | ||
``` | ||
|
||
The `focus()` method focuses the first focusable element within the Fragment, while `focusLast()` focuses the last focusable element. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dumb question but why can we easily get the root node, but not the parent node?
Wouldn't this be more flexible alternative to findDOMNode?
fragmentRef.current.parent.getRootNode()
?