-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Reapply "Merge pull request #81869 from software-mansion-labs/@zfurtak/migrate-NewChatPage" #84887
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
Changes from 6 commits
2160e65
958f304
8a33611
226877f
1247d40
d2a7863
71eea66
0a70657
a27e488
acf082f
07bf0e3
7cbdbec
f22a391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| diff --git a/node_modules/@shopify/flash-list/dist/recyclerview/RecyclerView.js b/node_modules/@shopify/flash-list/dist/recyclerview/RecyclerView.js | ||
| index c6056a1..af012e5 100644 | ||
| --- a/node_modules/@shopify/flash-list/dist/recyclerview/RecyclerView.js | ||
| +++ b/node_modules/@shopify/flash-list/dist/recyclerview/RecyclerView.js | ||
| @@ -69,6 +69,10 @@ const RecyclerViewComponent = (props, ref) => { | ||
| if (internalViewRef.current && firstChildViewRef.current) { | ||
| // Measure the outer and inner container layouts | ||
| const outerViewLayout = measureParentSize(internalViewRef.current); | ||
| + if (outerViewLayout.width === 0 && outerViewLayout.height === 0) { | ||
| + containerViewSizeRef.current = outerViewLayout; | ||
| + return; | ||
| + } | ||
| const firstChildViewLayout = measureFirstChildLayout(firstChildViewRef.current, internalViewRef.current); | ||
| containerViewSizeRef.current = outerViewLayout; | ||
| // Calculate offset of first item | ||
| @@ -99,6 +103,10 @@ const RecyclerViewComponent = (props, ref) => { | ||
| if (pendingChildIds.size > 0) { | ||
| return; | ||
| } | ||
| + if (((_a = containerViewSizeRef.current) === null || _a === void 0 ? void 0 : _a.width) === 0 && | ||
| + ((_b = containerViewSizeRef.current) === null || _b === void 0 ? void 0 : _b.height) === 0) { | ||
| + return; | ||
| + } | ||
| const layoutInfo = Array.from(refHolder, ([index, viewHolderRef]) => { | ||
| const layout = measureItemLayout(viewHolderRef.current, recyclerViewManager.tryGetLayout(index)); | ||
| // comapre height with stored layout | ||
| diff --git a/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx b/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx | ||
| index 73c9649..9e34907 100644 | ||
| --- a/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx | ||
| +++ b/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx | ||
| @@ -159,6 +159,16 @@ const RecyclerViewComponent = <T,>( | ||
| if (internalViewRef.current && firstChildViewRef.current) { | ||
| // Measure the outer and inner container layouts | ||
| const outerViewLayout = measureParentSize(internalViewRef.current); | ||
| + // Skip layout update when the container reports zero dimensions. | ||
| + // This happens when a parent has `display: none` applied (e.g., | ||
| + // React's <Activity mode="hidden"> in navigation stacks). Accepting | ||
| + // 0x0 would wipe the render stack and force a full re-initialization | ||
| + // when the container becomes visible again. | ||
| + if (outerViewLayout.width === 0 && outerViewLayout.height === 0) { | ||
| + // Record the zero size so the item measurement effect also bails out. | ||
| + containerViewSizeRef.current = outerViewLayout; | ||
| + return; | ||
| + } | ||
| const firstChildViewLayout = measureFirstChildLayout( | ||
| firstChildViewRef.current, | ||
| internalViewRef.current | ||
| @@ -198,6 +208,11 @@ const RecyclerViewComponent = <T,>( | ||
| if (pendingChildIds.size > 0) { | ||
| return; | ||
| } | ||
| + // Skip item measurement when the container is hidden (0x0). | ||
| + // See the guard in the layout params effect above. | ||
| + if (containerViewSizeRef.current?.width === 0 && containerViewSizeRef.current?.height === 0) { | ||
| + return; | ||
| + } | ||
| const layoutInfo = Array.from(refHolder, ([index, viewHolderRef]) => { | ||
| const layout = measureItemLayout( | ||
| viewHolderRef.current!, |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,8 +60,8 @@ function UserListItem<TItem extends ListItem>({ | |
| } | ||
| }, [item, onCheckboxPress, onSelectRow]); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | ||
| const [isReportInOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, { | ||
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- some utils that are used to get reportID return empty string "", which would make subscription to the whole collection with nullish coalescing operator, example of this could be found in NewChatPage.tsx where some hooks return reportID as empty strings | ||
| const [isReportInOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${item.reportID || undefined}`, { | ||
| selector: reportExistsSelector, | ||
| }); | ||
|
|
||
|
|
@@ -73,6 +73,9 @@ function UserListItem<TItem extends ListItem>({ | |
| const shouldUseIconPolicyID = !item.reportID && !item.accountID && !item.policyID; | ||
| const policyID = isThereOnlyWorkspaceIcon && shouldUseIconPolicyID ? String(item.icons?.at(0)?.id) : item.policyID; | ||
|
|
||
| const shouldDisableAccessibleGrouping = !!rightHandSideComponent && !canSelectMultiple; | ||
|
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.
Useful? React with 👍 / 👎.
Contributor
Author
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. Added that. |
||
|
|
||
| const contactAccessibilityLabel = item.text === item.alternateText ? (item.text ?? '') : [item.text, item.alternateText].filter(Boolean).join(', '); | ||
|
Contributor
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. ❌ CONSISTENCY-3 (docs)The accessibility label logic Import and call import {getAccessibilityLabel} from './BaseListItem';
// ...
const contactAccessibilityLabel = getAccessibilityLabel(item);(This requires exporting Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
Contributor
Author
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. Added that. |
||
| return ( | ||
| <BaseListItem | ||
| item={item} | ||
|
|
@@ -96,13 +99,19 @@ function UserListItem<TItem extends ListItem>({ | |
| keyForList={item.keyForList} | ||
| onFocus={onFocus} | ||
| shouldSyncFocus={shouldSyncFocus} | ||
| accessible={shouldDisableAccessibleGrouping ? false : undefined} | ||
| shouldDisableHoverStyle={shouldDisableHoverStyle} | ||
| > | ||
| {(hovered?: boolean) => { | ||
| const isHovered = !!hovered && !shouldDisableHoverStyle; | ||
|
|
||
| return ( | ||
| <> | ||
| <View | ||
| accessible={shouldDisableAccessibleGrouping || undefined} | ||
| accessibilityLabel={shouldDisableAccessibleGrouping ? contactAccessibilityLabel : undefined} | ||
| role={shouldDisableAccessibleGrouping ? CONST.ROLE.BUTTON : undefined} | ||
| style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]} | ||
| > | ||
| {!shouldUseDefaultRightHandSideCheckmark && !!canSelectMultiple && ( | ||
| <PressableWithFeedback | ||
| accessibilityLabel={item.text ?? ''} | ||
|
|
@@ -194,7 +203,7 @@ function UserListItem<TItem extends ListItem>({ | |
| </View> | ||
| </PressableWithFeedback> | ||
| )} | ||
| </> | ||
| </View> | ||
| ); | ||
| }} | ||
| </BaseListItem> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import {useIsFocused} from '@react-navigation/native'; | ||
| import {FlashList} from '@shopify/flash-list'; | ||
| import type {FlashListRef, ListRenderItemInfo} from '@shopify/flash-list'; | ||
| import React, {useImperativeHandle, useRef} from 'react'; | ||
| import React, {useCallback, useImperativeHandle, useRef} from 'react'; | ||
| import type {TextInputKeyPressEvent} from 'react-native'; | ||
| import {View} from 'react-native'; | ||
| import type {ValueOf} from 'type-fest'; | ||
|
|
@@ -101,23 +101,26 @@ function BaseSelectionListWithSections<TItem extends ListItem>({ | |
| hasKeyBeenPressed.current = true; | ||
| }; | ||
|
|
||
| const scrollToIndex = (index: number) => { | ||
| if (index < 0 || index >= flattenedData.length || !listRef.current) { | ||
| return; | ||
| } | ||
| const item = flattenedData.at(index); | ||
| if (!item) { | ||
| return; | ||
| } | ||
| try { | ||
| listRef.current.scrollToIndex({index}); | ||
| } catch (error) { | ||
| // FlashList may throw if layout for this index doesn't exist yet | ||
| // This can happen when data changes rapidly (e.g., during search filtering) | ||
| // The layout will be computed on next render, so we can safely ignore this | ||
| Log.warn('SelectionListWithSections: error scrolling to index', {error}); | ||
| } | ||
| }; | ||
| const scrollToIndex = useCallback( | ||
|
Contributor
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. ❌ CLEAN-REACT-PATTERNS-0 (docs)React Compiler is enabled in this codebase and automatically memoizes closures based on their captured variables. Wrapping Remove the const scrollToIndex = (index: number) => {
if (index < 0 || index >= flattenedData.length || !listRef.current) {
return;
}
// ...
};Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
Contributor
Author
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. It was added previously when linter was providing a warning, but now it can be removed since compiler does memoize this function correctly. |
||
| (index: number) => { | ||
| if (index < 0 || index >= flattenedData.length || !listRef.current) { | ||
| return; | ||
| } | ||
| const item = flattenedData.at(index); | ||
| if (!item) { | ||
| return; | ||
| } | ||
| try { | ||
| listRef.current.scrollToIndex({index}); | ||
| } catch (error) { | ||
| // FlashList may throw if layout for this index doesn't exist yet | ||
| // This can happen when data changes rapidly (e.g., during search filtering) | ||
| // The layout will be computed on next render, so we can safely ignore this | ||
| Log.warn('SelectionListWithSections: error scrolling to index', {error}); | ||
| } | ||
| }, | ||
| [flattenedData], | ||
| ); | ||
|
|
||
| const debouncedScrollToIndex = useDebounce(scrollToIndex, CONST.TIMING.LIST_SCROLLING_DEBOUNCE_TIME, {leading: true, trailing: true}); | ||
|
|
||
|
|
@@ -188,6 +191,10 @@ function BaseSelectionListWithSections<TItem extends ListItem>({ | |
| innerTextInputRef.current?.focus(); | ||
| }; | ||
|
|
||
| const clearInputAfterSelect = () => { | ||
| textInputOptions?.onChangeText?.(''); | ||
| }; | ||
|
|
||
| const updateAndScrollToFocusedIndex = (index: number, shouldScroll = true) => { | ||
| if (!shouldScroll) { | ||
| suppressNextFocusScrollRef.current = true; | ||
|
|
@@ -207,6 +214,8 @@ function BaseSelectionListWithSections<TItem extends ListItem>({ | |
|
|
||
| useImperativeHandle(ref, () => ({ | ||
| focusTextInput, | ||
| scrollToIndex, | ||
| clearInputAfterSelect, | ||
| updateAndScrollToFocusedIndex, | ||
| updateExternalTextInputFocus, | ||
| getFocusedOption: getFocusedItem, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.