Page MenuHomePhabricator

[native] Create a component for the media gallery on native
ClosedPublic

Authored by rohan on Jan 30 2023, 8:41 AM.
Tags
None
Referenced Files
Unknown Object (File)
Sat, Nov 23, 9:41 PM
Unknown Object (File)
Sat, Nov 23, 9:08 PM
Unknown Object (File)
Sat, Nov 23, 9:03 PM
Unknown Object (File)
Sat, Nov 23, 9:02 PM
Unknown Object (File)
Sat, Nov 23, 9:01 PM
Unknown Object (File)
Sat, Nov 23, 8:38 PM
Unknown Object (File)
Sat, Nov 23, 6:48 PM
Unknown Object (File)
Sat, Nov 9, 8:31 AM
Subscribers

Details

Summary

Here, we create the native component for the thread media gallery. Currently, mediaInfos is defaulted to an empty array, but it will be replaced to hold the media data in subsequent diffs. galleryItemGap refers to the gap between media, and galleryFirstItemGap refers to the first media in each row. galleryItemWidth is determined using Dimensions so we can hopefully ensure that each item takes 1/3 of the width of the gallery.

Encompassing the media in <TouchableOpacity> will be changed in later diffs, alongside just rendering an <Image> component.

Depends on D6465

Test Plan

This will be tested in the next diff, and a screenshot of what the media gallery will look like with sample, hard coded images will be shown.

Diff Detail

Repository
rCOMM Comm
Lint
No Lint Coverage
Unit
No Test Coverage

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
rohan requested review of this revision.Jan 30 2023, 8:55 AM
ashoat requested changes to this revision.Jan 30 2023, 12:21 PM
ashoat added inline comments.
native/chat/settings/thread-settings-media-gallery.react.js
20 ↗(On Diff #21619)

This won't update when the dimensions change. You should use useWindowDimensions instead

24 ↗(On Diff #21619)

We should use a FlatList instead of a ScrollView I think. It might be a little complicated since we might have multiple media displayed per line. We could either have each "line" be an entry in the FlatList, or we could try to find an open-source component that handles this well (eg. check https://reactnative.directory/)

26–27 ↗(On Diff #21619)

I personally don't like this sort of inline... it leads to unnecessarily deep indentation structures. What do you think of pulling it out?

30–38 ↗(On Diff #21619)

This will be regenerated on each render, forcing the View to rerender on every rerender of this component. Can we memoize it?

43–48 ↗(On Diff #21619)

Memoize

This revision now requires changes to proceed.Jan 30 2023, 12:21 PM
  1. Use useWindowDimensions instead of Dimensions for calculating the width of each media.
  2. Use a FlatList with numColumns={3} as opposed to using a ScrollView
  3. Remove in-line mapping as we're now using a FlatList
  4. Memoize the styles that are depending on constants, and remove the need for conditionals

@ashoat Let me know if this (especially in terms of the FlatList) was what you were thinking.

ashoat requested changes to this revision.Jan 31 2023, 6:16 AM

Ooh, numColumns is perfect!!

RE the rest – you need to think harder about memoizing. Can you please make sure that FlatList won't rerender when ThreadSettingsMediaGallery rerenders, unless it needs to?

This revision now requires changes to proceed.Jan 31 2023, 6:16 AM

RE the rest – you need to think harder about memoizing. Can you please make sure that FlatList won't rerender when ThreadSettingsMediaGallery rerenders, unless it needs to?

I considered wrapping the FlatList inside a React.useMemo() hook, but after doing some research, I thought it wouldn't be necessary. My understanding is that FlatList only re-renders when it's data prop is changed. Is encompassing the FlatList in a memo hook necessary here, as I may have misunderstood the docs and FlatList ends up re-renders each time ThreadSettingsMediaGallery re-renders?

I could memoize it with

const flatList = React.useMemo(() => { ... }, [mediaInfos, memoizedStyles, styles]}

You don't need to memoize the rendering of the FlatList, but you should think about what else you need to memoize here

You don't need to memoize the rendering of the FlatList, but you should think about what else you need to memoize here

I'll give it some more consideration, though one of my immediate thoughts is that renderItems is something to optimize here with React.useCallback to prevent unnecessary recomputing since the dependency array would hold [styles, memoizedStyles].

Explored uses of FlatList in the codebase to follow convention and use React.useCallback() for the renderItem prop of FlatList.

ashoat requested changes to this revision.Feb 1 2023, 10:30 AM

Much better!! You're still missing something that will cause Image to rerender every time renderItem is called

native/chat/settings/thread-settings-media-gallery.react.js
40 ↗(On Diff #21751)

No point updating this though – since children will be defined anew on every render, memoizing the style prop won't prevent the rerender

45 ↗(On Diff #21751)

This

This revision now requires changes to proceed.Feb 1 2023, 10:30 AM

Refactor array of styles and remove unused container styles

native/chat/settings/thread-settings-media-gallery.react.js
28 ↗(On Diff #21920)

In an effort to remove the array of styles, I'm using the ... operator to include the unboundStyles that are not related to any variables. I thought this would be better than in-lining a long list of styles

Still missing some memoization. We need to get you to a point where you can figure this out yourself, and you don't need repeated review cycles every time you introduce a hook. Can you figure out what you're doing that is causing the whole FlatList to rerender every time ThreadSettingsMediaGallery renders? Going forward, would be great if I don't have to request changes for memoization again

native/chat/settings/thread-settings-media-gallery.react.js
28 ↗(On Diff #21920)

Does this work with the "ID optimization" that StyleSheet.create is doing under the hood? Should we be using StyleSheet.compose or StyleSheet.flatten or something like that here

ashoat requested changes to this revision.Feb 3 2023, 8:57 AM
This revision now requires changes to proceed.Feb 3 2023, 8:57 AM

Can you figure out what you're doing that is causing the whole FlatList to rerender every time ThreadSettingsMediaGallery renders?

I've done some work and I've realized we need to memoize what's being rendered via renderItem, so extracting everything out of renderItem into a component with React.memo should do that. Then, within renderItems we just render the new component. I think that's the last bit of memoization I'm missing - sorry for the back and forth.

native/chat/settings/thread-settings-media-gallery.react.js
28 ↗(On Diff #21920)

Yes it should still work with the way that StyleSheet.create assigns an ID to each style. Even though they're being dynamically created, a unique ID should be assigned to the style object since StyleSheet.create is still called with the object. With the styles being memoized, a new ID should be assigned to them if they change

Extract what is being rendered by renderItems into a separate, memoized component with React.memo. Matched existing conventions
in the codebase as close as made sense here. This should prevent what is being returned inside renderItems from being
recreated each time ThreadSettingsMediaGallery re-renders

native/chat/settings/thread-settings-media-gallery.react.js
22 ↗(On Diff #22108)

Memoize galleryItemWidth as it depends on useWindowDimensions.width

native/chat/settings/thread-settings-media-gallery.react.js
24 ↗(On Diff #22111)

Updated the diff so the comment is in the previous revision, but this'll be refactored out in https://linear.app/comm/issue/ENG-2875/display-both-photos-and-videos-in-the-media-gallery-on-native, it's a placeholder for now. This would otherwise need memoization since the mediaInfos array has a new reference upon re-render and [] !== [] in JS since array comparison is done by reference.

ashoat requested changes to this revision.Feb 6 2023, 7:17 AM

You're definitely still confused about memoization. In this diff you've memoized things you don't need to memoize, and you skipped memoizing the only thing you actually needed to memoize.

I've done some work and I've realized we need to memoize what's being rendered via renderItem, so extracting everything out of renderItem into a component with React.memo should do that. Then, within renderItems we just render the new component. I think that's the last bit of memoization I'm missing - sorry for the back and forth.

I don't follow this. FlatList will only call renderItem if the actual item changes, so it's already "memoized".

native/chat/settings/thread-settings-media-gallery.react.js
21–23 ↗(On Diff #22111)

This does not need to be memoized. All you're doing is saving the CPU a tiny amount of arithmetic on rerender here. The fact that you memoized this shows you're still confused about memoization... memoizing a primitive will never help reduce rerenders, because primitive comparison is already a "deep comparison". Ie. it doesn't matter if you regenerate 5, React will still see that 5 === 5, and skip the rerender

24 ↗(On Diff #22111)

Memoize it please

53 ↗(On Diff #22111)

You should never use the Object type, this is basically any

This revision now requires changes to proceed.Feb 6 2023, 7:17 AM

This is now the fifth time I've requested changes on this diff regarding memoization. I highly suggest changing your meta-approach to revising this diff

Revert changes and just memoize mediaInfos now instead of later

Accepting this, but please make sure you understand memoization and how it affects rerenders going forward. My intuition from this diff is that we're "papering over" it right now... we are likely to have continued back-and-forths on every future React diff until you have a full grasp of it

This revision is now accepted and ready to land.Feb 6 2023, 8:18 AM

(FWIW https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/ was very useful for me (and I think others eg Abosh?) in understanding React rendering behavior/memoization/etc. There's a lot of stuff on eg StackOverflow and misc Medium blogs that's incorrect/inconsistent.)

In D6466#197440, @atul wrote:

(FWIW https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/ was very useful for me (and I think others eg Abosh?) in understanding React rendering behavior/memoization/etc. There's a lot of stuff on eg StackOverflow and misc Medium blogs that's incorrect/inconsistent.)

Thanks for the link, I'll be sure to give it a full read through

Update styling to match design - specifically have a 16px padding on either side of the FlatList, and adjust the math for galleryItemWidth to compensate.

Updated design:

Simulator Screen Shot - iPhone 14 Pro - 2023-02-09 at 11.32.09.png (2×1 px, 1 MB)

native/chat/settings/thread-settings-media-gallery.react.js
51–54 ↗(On Diff #22300)

An alternative to this approach is to have an array by index, i.e.
const stylesArrayByIndex = [memoizedStyles.mediaContainer, memoizedStyles.mediaContainerWithMargin, memoizedStyles.mediaContainerWithMargin];

then each View will have <View key={item.id} style={stylesArrayByIndex[i % 3]}>

native/chat/settings/thread-settings-media-gallery.react.js
16 ↗(On Diff #22300)

Minor nit since it's a primitive, but in general it's good to define things in the highest scope you can so that they don't end up being refined multiple times. In this case, I would move this definition to outside of the function

51–54 ↗(On Diff #22300)

Since you're using the constant 3 in multiple places, it would be good to extract it to some constants file

native/chat/settings/thread-settings-media-gallery.react.js
51–54 ↗(On Diff #22300)

Since 3 is scoped to this component, would it be best to just move it to a global declaration alongside galleryItemGap? Like const numColumns = 3 and use that instead?

native/chat/settings/thread-settings-media-gallery.react.js
51–54 ↗(On Diff #22300)

Yeah if it's only used in this component, then you only need to bother defining it once (in this component)

But make sure you're thinking critically about that... eg. if you have some other place where you effectively divide the screen into 3 rows without using the number 3 or something, we should refactor so that somebody could easily update 3 to eg. 5 and the code would all work correctly and display in 5 rows

Import FlatList from RNGH instead of RN and extract galleryItemGap and numColumns to globally declared constants.

columns = 3columns = 4columns = 5
Simulator Screen Shot - iPhone 14 Pro - 2023-02-10 at 15.26.59.png (2×1 px, 2 MB)
Simulator Screen Shot - iPhone 14 Pro - 2023-02-10 at 15.27.35.png (2×1 px, 1 MB)
Simulator Screen Shot - iPhone 14 Pro - 2023-02-10 at 15.27.58.png (2×1 px, 1 MB)
This revision was landed with ongoing or failed builds.Feb 26 2023, 11:45 AM
This revision was automatically updated to reflect the committed changes.