-
Notifications
You must be signed in to change notification settings - Fork 30
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
Section headers #34
base: master
Are you sure you want to change the base?
Section headers #34
Conversation
if (treatAsSections && prevProps.immutableData !== immutableData) { | ||
const flattenedData = utils.flattenMap(immutableData); | ||
this.setState({ | ||
flattenedData, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cooperka I chose to hold flattenedData
in state
so that it is only created once on each update to immutableData
, as the flattening transformation will be fairly expensive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One alternative, to avoid using state
at all, would be to use a WeakMap
to cache the results of the flattening transformation. As we are working with immutable data this would be easy to reason about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using state is fine, but if you put it in willUpdate
instead of didUpdate
I think it will be more performant (that's why the linter flagged this).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose didUpdate
because in the react docs for componentWillUpdate()
it says "Note that you cannot call this.setState()
here; nor should you do anything else (e.g. dispatch a Redux action) that would trigger an update to a React component before componentWillUpdate()
returns."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, componentWillReceiveProps
is what you should use here! Not willUpdate
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great @Leeds-eBooks! Thank you again for the help. I left several comments for whenever you find time to look it over.
if (treatAsSections && prevProps.immutableData !== immutableData) { | ||
const flattenedData = utils.flattenMap(immutableData); | ||
this.setState({ | ||
flattenedData, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using state is fine, but if you put it in willUpdate
instead of didUpdate
I think it will be more performant (that's why the linter flagged this).
.eslintrc
Outdated
@@ -8,7 +8,8 @@ | |||
}, | |||
|
|||
"rules": { | |||
"react-native/no-color-literals": "off" | |||
"react-native/no-color-literals": "off", | |||
"react/no-did-update-set-state": "off" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment below for how to avoid this. And just a general tip: it's preferable to type // eslint-disable-next-line react/no-did-update-set-state
above your code instead of setting a global rule 👍
* Whether to treat immutableData as a sectioned list, | ||
* applying section headers. | ||
*/ | ||
treatAsSections: PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can assume they want to render sections if renderSectionHeader
is truthy, so this prop can be removed. ListView does the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point!
* to be rendered as a section row. It will be passed the item, | ||
* and the item's key. | ||
*/ | ||
renderRow: PropTypes.func, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just leave it as renderItem
(i.e. remove this prop)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I think so.
flattenMap(data) { | ||
return data.reduce( | ||
(flattened, section, key) => flattened.set(key, section).merge(section), | ||
Immutable.OrderedMap().asMutable(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand but please clarify -- is asMutable
being used here for performance reasons, so we don't have to return a new Immutable object during each reduction step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes exactly, it's a little trick I learned after months of using immutablejs. Within our flattenMap
function, it's safe to make the collection mutable while we build it as long as we call asImmutable()
on it before we return it.
src/utils.js
Outdated
}, | ||
|
||
isSectionHeader(item) { | ||
return Immutable.Map.isMap(item) || Immutable.List.isList(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are Map and List necessarily section headers? I don't think I understand how this method works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I wasn't totally happy about this. In the flattenMap
method, I'm setting the value of the section header items to be their respective section, in other words:
Map {
section1: Map {
key1: val1,
key2: val2
},
section2: Map {
key3: val3,
key4: val4
}
}
// gets flattened to:
Map {
section1: Map {
key1: val1,
key2: val2
},
key1: val1,
key2: val2,
section2: Map {
key3: val3,
key4: val4
},
key3: val3,
key4: val4
}
One way, therefore, of identifying whether or not an item is a section header, is by querying if the item's value is a Map
or a List
. Do you think there's a better way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you happen to have a link to where the original ListView does something like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VirtualizedSectionList
does it like this: https://github.com/facebook/react-native/blob/master/Libraries/Lists/VirtualizedSectionList.js#L175-L224
For each index, it runs through the entire collection to work out if the index refers to a section header. I think this could be expensive, especially given that we are working with a Map
which has keys rather than indexes so we'd have to convert to an indexed collection too.
Actually, I think I can use stickyHeaderIndices
, as we have already computed these. Will put this in the next commit!
removed isSectionHeader() in favour of querying stickyHeaderIndices
I made a few minor style tweaks. I think I might be passing in the wrong data format because I get diff --git a/example/index.js b/example/index.js
index 3fd0fe8..e5abcac 100644
--- a/example/index.js
+++ b/example/index.js
@@ -1,7 +1,7 @@
import { AppRegistry } from 'react-native';
// Choose one:
-import App from './src/ImmutableListViewExample';
-// import App from './src/ImmutableVirtualizedListExample';
+// import App from './src/ImmutableListViewExample';
+import App from './src/ImmutableVirtualizedListExample';
AppRegistry.registerComponent('ImmutableListViewExample', () => App);
diff --git a/example/src/ImmutableVirtualizedListExample.js b/example/src/ImmutableVirtualizedListExample.js
index 5465c12..e5996c7 100644
--- a/example/src/ImmutableVirtualizedListExample.js
+++ b/example/src/ImmutableVirtualizedListExample.js
@@ -20,18 +20,23 @@ import utils from './utils';
*
*/
function ImmutableVirtualizedListExample() {
+ const sectionData = Immutable.fromJS({
+ sec1: [1, 2, 3],
+ });
+
return (
<GenericListExample
ListComponent={ImmutableVirtualizedList}
listComponentProps={{
renderItem: utils.renderItem,
+ renderSectionHeader: utils.renderSectionHeader,
keyExtractor: utils.trivialKeyExtractor,
}}
- initialDataA={Immutable.List(['Simple', 'List', 'of', 'Items'])}
+ initialDataA={sectionData}
dataMutatorA={(data) => data.set(3, 'This value was changed!')}
- initialDataB={Immutable.Range(1, 100)}
+ initialDataB={sectionData}
dataMutatorB={(data) => data.toSeq().map((n) => n * 2)}
/>
); |
Thanks for making these tweaks. I've been unexpectedly busy this week which is why I haven't had a chance to continue working on this, but it's really high on my to-do list! At the latest, I have 17th Feb free to work on this. Got to add tests, mainly. |
Oh, just seen your question! Will take a look at this asap. It's not obvious to me off the top of my head. |
It's all good, I know how life can be. Tests would be great, and will probably resolve my issue! Either by helping me understand the data format or by catching a bug ;) Take your time! |
Ok it's because my usage of |
Ok think this is working now! |
255bb12
to
efdc474
Compare
Just catching up with this – does anything remain to be done? Does it need more tests do you think? |
@cooperka Ah yes, I never got round to testing with a live app – just doing that now. Is there a nicer way to make changes in the package and see it updated in the example app than killing the app and running |
In an earlier version of RN + yarn you could simply It might work to |
I know there are other automated solutions; if you're familiar with one feel free to recommend it. I've never needed something like that until now since the npm trick used to work. |
? renderSectionHeader | ||
: renderItem; | ||
|
||
return renderMethod(info.item, this.keyExtractor(info.item, info.index)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok the problem stems from this line. In your example app, you’re (understandably!) assuming that renderItem()
will be called with the whole info
object (as it is if we are dealing with a flat list without section headers, 3 lines below this comment). Will push a fix asap!
…` as we do for flat lists
The items display now, but it shows |
Yeah sorry, got to change something in the example app's code. Coming right up 🙂 |
Ah I think I remember now why I didn't originally pass the whole |
My intuition is that we should use the same data format conventions as the original ListView, but the function arguments should be formatted like Eventually I'd love to release My recommendation for this PR (feel free to suggest changes): // This data:
Immutable.fromJS({
sec1: [1, 2, 3],
})
// Or, equivalently:
Immutable.fromJS({
sec1: { r1: 1, r2: 2, r3: 3 },
}) Would render as The methods would be called as follows: renderSectionHeader({ sectionData, sectionID })
renderItem({ item, index }) So What do you think? |
@cooperka and @benadamstyles: Have you made any progress on this? I would love to see section headers implemented, and I like the shape of the input data. Do you need any help pushing this over the goal line? |
We could really use this functionality as well. We're currently using ImmutableListView, but we're seeing some performance issues. |
This is just my initial commit – it would be really helpful if @cooperka you could take a glance at this before I continue. I have had to make some possibly controversial decisions, most notably introducing a
setState
incomponentDidUpdate
– for which I had to make a change in.eslintrc
.I welcome any comments, now matter how challenging they are!