Open yepMad opened 1 year ago
This also happens to <ScrollView>
, it freezes the component as intended but every time you unfreeze it, it scrolls to the top.
This issue appears to occur because Suspense
is adding the style display: 'none'
to its suspended children. We worked around the issue with something like the following.
const [freeze, setFreeze] = useState(false);
const [placeholder, setPlaceholder] = useState(null);
const handleFreeze = (value: boolean) => {
setFreeze(value);
if (value) {
const __html = document.getElementById('freezeBlock')?.innerHTML ?? '';
setPlaceholder(<div dangerouslySetInnerHTML={{ __html }} />);
} else {
setPlaceholder(null);
}
};
return (
<Freeze freeze={freeze} placeholder={placeholder}>
<div id="freezeBlock">{children}</div>
</Freeze>
);
I think the root of the issue lies with React.Suspense
not providing a better way to opt out of having suspended children hidden by default. There is the useTransition
hook, but I wasn't able to figure out how to get that to work with the concept of a boolean value that triggers suspension.
This issue appears to occur because
Suspense
is adding the styledisplay: 'none'
to its suspended children. We worked around the issue with something like the following.const [freeze, setFreeze] = useState(false); const [placeholder, setPlaceholder] = useState(null); const handleFreeze = (value: boolean) => { setFreeze(value); if (value) { const __html = document.getElementById('freezeBlock')?.innerHTML ?? ''; setPlaceholder(<div dangerouslySetInnerHTML={{ __html }} />); } else { setPlaceholder(null); } }; return ( <Freeze freeze={freeze} placeholder={placeholder}> <div id="freezeBlock">{children}</div> </Freeze> );
I think the root of the issue lies with
React.Suspense
not providing a better way to opt out of having suspended children hidden by default. There is theuseTransition
hook, but I wasn't able to figure out how to get that to work with the concept of a boolean value that triggers suspension.
Thank you so much for this! Any idea how it can be solved on mobile?
Thank you so much for this! Any idea how it can be solved on mobile?
@yepMad
By mobile, do you mean something like react-native? I've only worked with React for strict web developement. I'm not sure if this is available in react-native or not, but I had a coworker just mention Mutation Observers. It's funny all the basic things you keep learning about in the javascript ecosystem. Anyways, I was able to refactor the previous code so that the dom isn't being duplicated anymore. Using the mutation observer, we can catch Suspense adding display: 'none !important
and prevent it from happening. This could could probably be refined, but hopefully the gist of it is clear enough.
let observer: MutationObserver | undefined;
export function FreezeProvider({ children, freeze }: { children: React.ReactElement, freeze?: boolean }) {
useEffect(() => {
if (observer) return;
observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== 'attributes' || mutation.attributeName !== 'style') continue;
const target = mutation.target as HTMLElement;
if (target.getAttribute('style')?.includes('display: none !important;'))
target.setAttribute('style', '');
}
});
// this is used to ensure the mutation observer is correctly assigned
const fetchFreezeBlockInterval = setInterval(() => {
const freezeBlockEl = document.getElementById('freezeBlock');
if (!freezeBlockEl || !observer) return;
observer.observe(freezeBlockEl, {
attributeFilter: ['style'],
});
clearInterval(fetchFreezeBlockInterval);
}, 1000);
}, []);
return (
<Freeze freeze={freeze} placeholder={null}>
<div id="freezeBlock">{children}</div>
</Freeze>
);
}
@bkdiehl Oh, sorry. I meant react-native. This solution is only for React Web, right?
@bkdiehl Your component will keep children freezed only if props don't change, but if some of child components are subscribed to some state update (e.g. using WebSockets) it will still be re-rendering in the background.
Hello,
My current component is structured as follows, take into account that isFocused comes from the React Navigation hook:
When the user returns to this screen after being frozen, the FlatList scroll is at the top, is this the expected behavior? From the README I understood that it shouldn't happen. Here is a video to illustrate the problem. For tabs I'm using @react-navigation/material-top-tabs.
https://user-images.githubusercontent.com/22564368/206342342-98c999f5-a602-4930-88d2-092abb2756a3.mp4