-
Notifications
You must be signed in to change notification settings - Fork 49.3k
Add observer methods to fragment instances #32619
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
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
96 changes: 96 additions & 0 deletions
96
fixtures/dom/src/components/fixtures/fragment-refs/EventListenerCase.js
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 |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import TestCase from '../../TestCase'; | ||
import Fixture from '../../Fixture'; | ||
|
||
const React = window.React; | ||
const {Fragment, useEffect, useRef, useState} = React; | ||
|
||
function WrapperComponent(props) { | ||
return props.children; | ||
} | ||
|
||
function handler(e) { | ||
const text = e.currentTarget.innerText; | ||
alert('You clicked: ' + text); | ||
} | ||
|
||
export default function EventListenerCase() { | ||
const fragmentRef = useRef(null); | ||
const [extraChildCount, setExtraChildCount] = useState(0); | ||
|
||
useEffect(() => { | ||
fragmentRef.current.addEventListener('click', handler); | ||
|
||
const lastFragmentRefValue = fragmentRef.current; | ||
return () => { | ||
lastFragmentRefValue.removeEventListener('click', handler); | ||
}; | ||
}); | ||
|
||
return ( | ||
<TestCase title="Event Registration"> | ||
<TestCase.Steps> | ||
<li>Click one of the children, observe the alert</li> | ||
<li>Add a new child, click it, observe the alert</li> | ||
<li>Remove the event listeners, click a child, observe no alert</li> | ||
<li>Add the event listeners back, click a child, observe the alert</li> | ||
</TestCase.Steps> | ||
|
||
<TestCase.ExpectedResult> | ||
<p> | ||
Fragment refs can manage event listeners on the first level of host | ||
children. This page loads with an effect that sets up click event | ||
hanndlers on each child card. Clicking on a card will show an alert | ||
with the card's text. | ||
</p> | ||
<p> | ||
New child nodes will also have event listeners applied. Removed nodes | ||
will have their listeners cleaned up. | ||
</p> | ||
</TestCase.ExpectedResult> | ||
|
||
<Fixture> | ||
<div className="control-box"> | ||
<div>Target count: {extraChildCount + 3}</div> | ||
<button | ||
onClick={() => { | ||
setExtraChildCount(prev => prev + 1); | ||
}}> | ||
Add Child | ||
</button> | ||
<button | ||
onClick={() => { | ||
fragmentRef.current.addEventListener('click', handler); | ||
}}> | ||
Add click event listeners | ||
</button> | ||
<button | ||
onClick={() => { | ||
fragmentRef.current.removeEventListener('click', handler); | ||
}}> | ||
Remove click event listeners | ||
</button> | ||
<div class="card-container"> | ||
<Fragment ref={fragmentRef}> | ||
<div className="card" id="child-a"> | ||
Child A | ||
</div> | ||
<div className="card" id="child-b"> | ||
Child B | ||
</div> | ||
<WrapperComponent> | ||
<div className="card" id="child-c"> | ||
Child C | ||
</div> | ||
{Array.from({length: extraChildCount}).map((_, index) => ( | ||
<div className="card" id={'extra-child-' + index} key={index}> | ||
Extra Child {index} | ||
</div> | ||
))} | ||
</WrapperComponent> | ||
</Fragment> | ||
</div> | ||
</div> | ||
</Fixture> | ||
</TestCase> | ||
); | ||
} |
153 changes: 153 additions & 0 deletions
153
fixtures/dom/src/components/fixtures/fragment-refs/IntersectionObserverCase.js
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 |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import TestCase from '../../TestCase'; | ||
import Fixture from '../../Fixture'; | ||
|
||
const React = window.React; | ||
const {Fragment, useEffect, useRef, useState} = React; | ||
|
||
function WrapperComponent(props) { | ||
return props.children; | ||
} | ||
|
||
function ObservedChild({id}) { | ||
return ( | ||
<div id={id} className="observable-card"> | ||
{id} | ||
</div> | ||
); | ||
} | ||
|
||
const initialItems = [ | ||
['A', false], | ||
['B', false], | ||
['C', false], | ||
]; | ||
|
||
export default function IntersectionObserverCase() { | ||
const fragmentRef = useRef(null); | ||
const [items, setItems] = useState(initialItems); | ||
const addedItems = items.slice(3); | ||
const anyOnScreen = items.some(([, onScreen]) => onScreen); | ||
const observerRef = useRef(null); | ||
|
||
useEffect(() => { | ||
if (observerRef.current === null) { | ||
observerRef.current = new IntersectionObserver( | ||
entries => { | ||
setItems(prev => { | ||
const newItems = [...prev]; | ||
entries.forEach(entry => { | ||
const index = newItems.findIndex( | ||
([id]) => id === entry.target.id | ||
); | ||
newItems[index] = [entry.target.id, entry.isIntersecting]; | ||
}); | ||
return newItems; | ||
}); | ||
}, | ||
{ | ||
threshold: [0.5], | ||
} | ||
); | ||
} | ||
fragmentRef.current.observeUsing(observerRef.current); | ||
|
||
const lastFragmentRefValue = fragmentRef.current; | ||
return () => { | ||
lastFragmentRefValue.unobserveUsing(observerRef.current); | ||
observerRef.current = null; | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<TestCase title="Intersection Observer"> | ||
<TestCase.Steps> | ||
<li> | ||
Scroll the children into view, observe the sidebar appears and shows | ||
which children are in the viewport | ||
</li> | ||
<li> | ||
Add a new child and observe that the Intersection Observer is applied | ||
</li> | ||
<li> | ||
Click Unobserve and observe that the state of children in the viewport | ||
is no longer updated | ||
</li> | ||
<li> | ||
Click Observe and observe that the state of children in the viewport | ||
is updated again | ||
</li> | ||
</TestCase.Steps> | ||
|
||
<TestCase.ExpectedResult> | ||
<p> | ||
Fragment refs manage Intersection Observers on the first level of host | ||
children. This page loads with an effect that sets up an Inersection | ||
Observer applied to each child card. | ||
</p> | ||
<p> | ||
New child nodes will also have the observer applied. Removed nodes | ||
will be unobserved. | ||
</p> | ||
</TestCase.ExpectedResult> | ||
<Fixture> | ||
<button | ||
onClick={() => { | ||
setItems(prev => [ | ||
...prev, | ||
[`Extra child: ${prev.length + 1}`, false], | ||
]); | ||
}}> | ||
Add Child | ||
</button> | ||
<button | ||
onClick={() => { | ||
setItems(prev => { | ||
if (prev.length === 3) { | ||
return prev; | ||
} | ||
return prev.slice(0, prev.length - 1); | ||
}); | ||
}}> | ||
Remove Child | ||
</button> | ||
<button | ||
onClick={() => { | ||
fragmentRef.current.observeUsing(observerRef.current); | ||
}}> | ||
Observe | ||
</button> | ||
<button | ||
onClick={() => { | ||
fragmentRef.current.unobserveUsing(observerRef.current); | ||
setItems(prev => { | ||
return prev.map(item => [item[0], false]); | ||
}); | ||
}}> | ||
Unobserve | ||
</button> | ||
{anyOnScreen && ( | ||
<div className="fixed-sidebar card-container"> | ||
<p> | ||
<strong>Children on screen:</strong> | ||
</p> | ||
{items.map(item => ( | ||
<div className={`card ${item[1] ? 'onscreen' : null}`}> | ||
{item[0]} | ||
</div> | ||
))} | ||
</div> | ||
)} | ||
<Fragment ref={fragmentRef}> | ||
<ObservedChild id="A" /> | ||
<WrapperComponent> | ||
<ObservedChild id="B" /> | ||
</WrapperComponent> | ||
<ObservedChild id="C" /> | ||
{addedItems.map((_, index) => ( | ||
<ObservedChild id={`Extra child: ${index + 4}`} /> | ||
))} | ||
</Fragment> | ||
</Fixture> | ||
</TestCase> | ||
); | ||
} |
63 changes: 63 additions & 0 deletions
63
fixtures/dom/src/components/fixtures/fragment-refs/ResizeObserverCase.js
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 |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import TestCase from '../../TestCase'; | ||
import Fixture from '../../Fixture'; | ||
|
||
const React = window.React; | ||
const {Fragment, useEffect, useRef, useState} = React; | ||
|
||
export default function ResizeObserverCase() { | ||
const fragmentRef = useRef(null); | ||
const [width, setWidth] = useState([0, 0, 0]); | ||
|
||
useEffect(() => { | ||
const resizeObserver = new window.ResizeObserver(entries => { | ||
if (entries.length > 0) { | ||
setWidth(prev => { | ||
const newWidth = [...prev]; | ||
entries.forEach(entry => { | ||
const index = parseInt(entry.target.id, 10); | ||
newWidth[index] = Math.round(entry.contentRect.width); | ||
}); | ||
return newWidth; | ||
}); | ||
} | ||
}); | ||
|
||
fragmentRef.current.observeUsing(resizeObserver); | ||
const lastFragmentRefValue = fragmentRef.current; | ||
return () => { | ||
lastFragmentRefValue.unobserveUsing(resizeObserver); | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<TestCase title="Resize Observer"> | ||
<TestCase.Steps> | ||
<li>Resize the viewport width until the children respond</li> | ||
<li>See that the width data updates as they elements resize</li> | ||
</TestCase.Steps> | ||
<TestCase.ExpectedResult> | ||
The Fragment Ref has a ResizeObserver attached which has a callback to | ||
update the width state of each child node. | ||
</TestCase.ExpectedResult> | ||
<Fixture> | ||
<Fragment ref={fragmentRef}> | ||
<div className="card" id="0" style={{width: '100%'}}> | ||
<p> | ||
Width: <b>{width[0]}px</b> | ||
</p> | ||
</div> | ||
<div className="card" id="1" style={{width: '80%'}}> | ||
<p> | ||
Width: <b>{width[1]}px</b> | ||
</p> | ||
</div> | ||
<div className="card" id="2" style={{width: '50%'}}> | ||
<p> | ||
Width: <b>{width[2]}px</b> | ||
</p> | ||
</div> | ||
</Fragment> | ||
</Fixture> | ||
</TestCase> | ||
); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
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.
This is just moving into a new module from index.js