diff --git a/src/screens/PostThread/index.tsx b/src/screens/PostThread/index.tsx index 022555be64..7dc3a0fffc 100644 --- a/src/screens/PostThread/index.tsx +++ b/src/screens/PostThread/index.tsx @@ -142,6 +142,8 @@ export function Inner({uri}: {uri: string | undefined}) { const anchorOffsetTop = anchor.getBoundingClientRect().top const headerHeight = header.getBoundingClientRect().height + console.log({anchorOffsetTop}) + /* * `deferParents` is `true` on a cold load, and always reset to * `true` when params change via `prepareForParamsUpdate`. @@ -157,6 +159,12 @@ export function Inner({uri}: {uri: string | undefined}) { * `headerHeight`) will result in a _negative_ `offset` value, which will * scroll the anchor post _down_ to the top of the screen. * + * However, `prepareForParamsUpdate` also resets scroll to `0`, so when a user + * changes params, the anchor post's offset will actually be equivalent + * to the `headerHeight` because of how the DOM is stacked on web. + * Therefore, `anchorOffsetTop - headerHeight` will once again be 0, + * which means the first pass in this case will result in no scroll. + * * Then, once parents are prepended, this will fire again. Now, the * `anchorOffsetTop` will be positive, which minus the header height, * will give us a _positive_ offset, which will scroll the anchor post @@ -186,7 +194,11 @@ export function Inner({uri}: {uri: string | undefined}) { if (list && anchor && shouldHandleScroll.current) { /* * `prepareForParamsUpdate` is called any time the user changes thread params like - * `view` or `sort`, which sets `deferParents(true)`. + * `view` or `sort`, which sets `deferParents(true)` and resets the + * scroll to the top of the list. However, there is a split second + * where the top of the list is wherever the parents _just were_. So if + * there were parents, the anchor is not at the top of the list just + * prior to this handler being called. * * Once this handler is called, the anchor post is the first item in * the list (because of `deferParents` being `true`), and so we can @@ -199,21 +211,35 @@ export function Inner({uri}: {uri: string | undefined}) { }) /* - * Because we reset `deferParents` (so the anchor is the first item in - * the list), the anchor post will retain its position because of - * `maintainVisibleContentPosition` handling on native. So we don't need - * to let this handler run again, like we do on web. + * After this first pass, `deferParents` will be `false`, and those + * will render in. However, the anchor post will retain its position + * because of `maintainVisibleContentPosition` handling on native. So we + * don't need to let this handler run again, like we do on web. */ shouldHandleScroll.current = false } }) + /** + * Called any time the user changes thread params, such as `view` or `sort`. + * Prepares the UI for repositioning of the scroll so that the anchor post is + * always at the top after a params change. + * + * No need to handle max parents here, deferParents will handle that and we + * want it to re-render with the same items above the anchor. + */ const prepareForParamsUpdate = useCallback(() => { + /** + * Truncate list so that anchor post is the first item in the list. Manual + * scroll handling on web is predicated on this, and on native, this allows + * `maintainVisibleContentPosition` to do its thing. + */ setDeferParents(true) - setMaxParentCount(PARENT_CHUNK_SIZE) + // reset this to a lower value for faster re-render setMaxChildrenCount(CHILDREN_CHUNK_SIZE) + // set flag shouldHandleScroll.current = true - }, [setDeferParents, setMaxParentCount, setMaxChildrenCount]) + }, [setDeferParents, setMaxChildrenCount]) const setSortWrapped = useCallback( (sort: string) => { @@ -233,6 +259,8 @@ export function Inner({uri}: {uri: string | undefined}) { const onStartReached = () => { if (thread.state.isFetching) return + // can be true after `prepareForParamsUpdate` is called + if (deferParents) return // prevent any state mutations if we know we're done if (maxParentCount >= totalParentCount.current) return setMaxParentCount(n => n + PARENT_CHUNK_SIZE) @@ -240,6 +268,8 @@ export function Inner({uri}: {uri: string | undefined}) { const onEndReached = () => { if (thread.state.isFetching) return + // can be true after `prepareForParamsUpdate` is called + if (deferParents) return // prevent any state mutations if we know we're done if (maxChildrenCount >= totalChildrenCount.current) return setMaxChildrenCount(prev => prev + CHILDREN_CHUNK_SIZE) @@ -323,7 +353,7 @@ export function Inner({uri}: {uri: string | undefined}) { ) } else if (item.depth === 0) { return ( - <> + - + ) } else { if (thread.state.view === 'tree') { @@ -458,11 +488,18 @@ export function Inner({uri}: {uri: string | undefined}) { ListFooterComponent={ }