Open likern opened 4 years ago
As stated here using react hooks not in the top level of a function component is considered as a bad practice and should be avoided. If I understand correctly that excludes using them on demand like you would like to. Couldn't your example be done creating a certain amount of refs and reassigning them?
As stated here using react hooks not in the top level of a function component is considered as a bad practice and should be avoided. If I understand correctly that excludes using them on demand like you would like to. Couldn't your example be done creating a certain amount of refs and reassigning them?
From my understanding it's not possible.
I use ref reassign to measure
only last page. But as soon as last page is deleted, I have to somewhere get references from penultimate page (references to it's first and last rows). Not only references, but filled references (which points to actual Animated.View
's). I need to store references to all first / last rows of all rendered pages.
The algorithm to decide when to add page is controlled by component customer (it's dynamic), something like this:
// Any custom logic provided by customer
const isAppendPageToEnd = (event: NativeScrollEvent) => {
'worklet';
const divider = 3;
const halfOfContent = (1 - 1 / divider) * event.contentSize.height;
const halfOfScrollView = (1 - 1 / divider) * event.layoutMeasurement.height;
const centerYOffset = halfOfContent - event.contentOffset.y;
if (centerYOffset < halfOfScrollView) {
return [true, halfOfScrollView, centerYOffset];
}
return [false, halfOfScrollView, centerYOffset];
};
So we don't know number of pages beforehand.
My algorithm for now is the following: Whenever new page is added (with N rows, where N can be dynamic):
Animated.View
(to use measure
later)Map
(to effeciently add / delete rows of pages)onScroll
event do calculations to decide whether to add new page or delete the last pagemeasure
for active references (refs to first / last rows of last page, and first / last rows of fist page)const TrackedRow = React.forwardRef<Animated.View, TrackedRowProps>(
(props, ref) => {
return <Animated.View ref={ref}>{props.children}</Animated.View>;
}
);
let ref: React.RefObject<Animated.View> | undefined;
if (pages.size === 0) {
if (isFirstRow) {
ref = firstPageFirstRowRef;
} else if (isLastRow) {
ref = firstPageLastRowRef;
lastPageLastRowRefData.current = data[i];
}
} else {
if (isFirstRow) {
ref = lastPageFirstRowRef;
lastPageFirstRowRefData.current = data[i];
} else if (isLastRow) {
ref = lastPageLastRowRef;
// console.log(`last row: ${JSON.stringify(data[i])}`);
lastPageLastRowRefData.current = data[i];
}
}
const trackedRow = React.createElement(
TrackedRow,
{
key,
ref
},
row
);
row = trackedRow;
Draft how to delete rows by pages.
const pageIdsRef = useRef<number[]>([]);
const [pages] = useState<Map<number, Key[]>>(createMap);
const [rows] = useState<Map<Key, React.ReactElement<any>>>(createMap);
const deleteRowsOfFirstPage = useCallback(() => {
console.log('onFirstPageDelete: function start');
if (pageIdsRef.current.length > 0) {
const pageId = pageIdsRef.current.shift();
if (pageId !== undefined) {
const pageRows = pages.get(pageId);
if (pageRows !== undefined) {
pageRows.forEach((pageRow) => {
rows.delete(pageRow);
});
}
}
rerender();
}
}, [rows, pages, pageIdsRef, rerender]);
I think one approach would be through using https://en.reactjs.org/docs/refs-and-the-dom.html#callback-refs.
Animated.View
measure
directly with that component / tagBut for that to working measure
should take not only references, created by useAnimatedRef
, but component / tag directly (what is provided in callback reference)
Hi @likern
This issue is a bit stale, but I think you are touching on a potentially valuable topic.
Let me try to rephrase the issue a little bit to see if I understand it properly since there seems to be a lot of context, that I may be missing.
What you would like to have in reanimated 2 is another way to declare refs, apart from useAnimatedRef
which is similar to useRef
from React. You are vouching to add one of two:
<Component ref={ref => foo = ref} />
to be used in Animated ComponentsReact.createRef()
but for Reanimated 2To extend a bit, you would like to have it because you want to store multiple refs, and you don't know how many of them you want to store (perhaps how many comes from the API) and you want them to be mutable so you want to have a convenient way to re-assign them.
Let me know if I am correct here and correct me if I'm wrong.
Thank you for creating the feature request, have a nice day Victor!
@jkadamczyk 👋🏻 Yes, you are right.
To extend a bit, you would like to have it because you want to store multiple refs, and you don't know how many of them you want to store (perhaps how many comes from the API) and you want them to be mutable so you want to have a convenient way to re-assign them.
Yes. Not sure about reassignment (the original code is gone).
a callback option to assign ref anywhere like with <Component ref={ref => foo = ref} /> to be used in Animated Components
Yes.
something similar to React.createRef() but for Reanimated 2
Yes.
Ability to create animated refs dynamically (for example for every list element)
Ability to create at any code (in callbacks or conditionally) without hooks restrictions.
It can be implemented like React.createRef()
, but not required.
The original idea was
<View />
(for every list element or conditionally)<View />
into <Animated.View />
<Animated.View />
to track <View />
ref.measure()
and all worklet stuff on UI threadThis Feature Request helps
Implement <FlatList />
purely on UI thread using <View />
as base component. And managing adding / deleting row ourselves.
Implement viewabilityConfig
and onViewableItemsChanged
on UI thread https://reactnative.dev/docs/virtualizedlist#onviewableitemschanged.
I'm having the same issue. I need to define multiple animated "scrollviews" that are coming dynamically, but I need to have their refs in order to have access to their "scrollTo" method.
I tried something like this:
const x_refs_array = new Array(SCREENS.length).fill(useAnimatedRef());
const handleSubscreenXScroll = useAnimatedScrollHandler(() => {
console.log(x_refs_array[0]()) // -1
})
// ...
return (
<Animated.ScrollView
// onScroll={handleScreenYScroll}
horizontal
ref={y_ref}
>
{dynamicsXScrollSections.map((content, index) => (
<Animated.ScrollView
// onScroll={handleSectionXScroll}
ref={(ref) => (x_refs_array[index].current = ref)} // <----- here
>
{data}
</Animated.ScrollView>
))}
</Animated.ScrollView>
);
Hi! Is there any updates? Or maybe there are some known approaches how to achieve createRef behavior (means dynamically created animated refs)?
I'd like to second this.
export function useAnimatedRef<T extends ComponentRef>(): RefObjectFunction<T> {
const tag = useSharedValue<number | ShadowNodeWrapper | null>(-1);
const ref = useRef<RefObjectFunction<T>>();
if (!ref.current) {
const fun: RefObjectFunction<T> = <RefObjectFunction<T>>((component) => {
// enters when ref is set by attaching to a component
if (component) {
tag.value = getTagValueFunction(getComponentOrScrollableRef(component));
fun.current = component;
}
return tag.value;
});
fun.current = null;
const remoteRef = makeShareableCloneRecursive({
__init: () => {
'worklet';
return () => tag.value;
},
});
registerShareableMapping(fun, remoteRef);
ref.current = fun;
}
return ref.current;
}
Looking at useAnimatedRef()
's code, I don't understand what exactly remoteRef
is about. But the relevant part for scrollTo
seems to be the tag number, since that's what the RefObjectFunction
returns when called.
scrollTo = ( animatedRef: RefObjectFunction<Component>, x: number, y: number, animated: boolean) => {
'worklet';
if (!_WORKLET) { return; }
// Calling animatedRef on Paper returns a number (nativeTag)
const viewTag = animatedRef() as number;
_scrollTo(viewTag, x, y, animated);
};
So I think that just exposing the ability to get a tag for a component should be enough for most usecases?
Animated.getTagForComponent<C extends ComponentRef> = (component: C) => getTagValueFunction(getComponentOrScrollableRef(component))
Description
For using infinite lists (which not only add new rows on demand, but also delete old ones) it's required to create animated refs dynamically. Now it's only allowed to use
useAnimatedRef
which can't be created on-demand.Let's say I added one page (rows are grouped by pages). I wrap first and last rows of page into
Animated.View
to measure it's position insideonScroll
.That way I can understand the height of view , and track it's position.
If page is shown in viewport of ScrollView - I can add another page.
Caveats
Motivation
High-performance scrolling, including infinite and cycled lists.
Code Example