I want to use this component to let a user navigate through a (potentially endless) stream of items and as such I am adding new items to the data array as the user nears the end of the list and this works just perfectly. Obviously though I can't just keep adding items without removing older ones forever as this will exhaust memory at some point.
Removing items from the start of the data array seems to work pretty well except for the fact that it seems to act very strange with the index system. The problem is that the index doesn't change and after navigation there is a huge jump to the position of the old index+1. It would be nice if the component could automatically deal with this or provide me with a way to decrement the index without visually navigating.
Hi @Gerharddc,
I totally get what you're after, but this might be tricky to implement.
Would you mind providing me with a simple example that would serve as a starting point to work on this feature? Something based on the provided example maybe...
@bd-arc I'll have to see about getting it integrated into the example but I can already give you the relevant code that I tested with yesterday:
let idx = 0;
export default class Test extends React.Component {
private getViewport() {
return {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
};
}
state = {
viewport: this.getViewport(),
lys: [
{ name: 'One' },
{ name: 'Two' },
{ name: 'Three' },
{ name: 'Four' },
],
};
private updateDimensions() {
this.setState({
viewport: this.getViewport(),
});
}
private renderItem({ item, index }: { item: IItem, index: number }) {
return (
<View>
<Image source={require('../../assets/img/cat.jpg')}
style={{width: this.state.viewport.width - 100, height: this.state.viewport.width - 100}}/>
<Text>{item.name}</Text>
</View>
);
}
private handleIndexChange(index: number) {
const lys = this.state.lys.slice();
if (index >= (lys.length - 2)) {
idx++;
lys.push({ name: idx.toString() });
}
if (index >= 5) {
lys.splice(0, 1);
}
this.setState({ lys });
}
public render() {
return (
<View style={{ flex: 1 }}
<Text>Flow</Text>
<Carousel
data={this.state.lys}
renderItem={this.renderItem.bind(this)}
sliderWidth={this.state.viewport.width}
itemWidth={this.state.viewport.width - 100}
onSnapToItem={this.handleIndexChange.bind(this)}
/>
</View>
);
}
}
The slightly strange syntax is just TypeScript.
This code adds a new item just before you reach the end of the carousel and also removes an item for each one that it adds. If you run this and try to scroll to the next item after 1 has been 1 then it will suddenly jump another item ahead because the index of the item that you were looking at before the interaction has actually moved 1 down. If you were to remove more than 1 item at a time this effects just becomes worse.
Hi @Gerharddc,
I've gave your issue some thought, but I don't see an easy way to deal with it (particularly with the recent loop addition which just made index handling even trickier). There is no definitive way of knowing the index that should be returned by a particular item after various additions/removals.
Do you have any suggestion about how this problem might be tackled?
@bd-arc I can definitely notify the carousel of exactly how many items I will be removing before, after or during the change. In this way it should be able to adjust adjust the internal index accordingly. Unfortunately I am unfamiliar with the code-base and do not know if this is something that can easily be implemented.
In theory I could get away with just a method to notify the carousel to adjust the index backwards by a certain amount.
@Gerharddc Thanks for the feedback! I'll see if I can think of something that doesn't break the existing logic.
FYI, index handling already consists of more than 100 lines of code...
any progress on this? It looks like I ran into the same issue: when adding element to the data array, the carousel is buggy
I've been able to work around my issue by using this._carousel.snapToItem(index + 1, false), maybe it can help you!
Hi @m-vdb,
I didn't have time to work on this issue lately, but if the problem is spreading I'll try to take a look at it shortly. Not before next week though, since I currently have major professional deadlines to meet ;-)
having the same issue, when deleting the last item a blank slide was displayed until you started scrolling again. Solved it by adopting @m-vdb solution, but it feels hacky.
Still, great job and thanks for this lib.
componentDidUpdate(prevProps, prevState) {
if (this.props.data.length < prevProps.data.length) {
this._carousel.snapToItem(this.props.passengers.length, false);
}
}
If use same data, or when the data length is 1 and we add data items, there will be buggy too, inspired by @raduflp , I use following hack code to treat these situations
// for same data buggy situation, use only length change or data change time
if (!isEqual(oriData, data)) {
// for length is 1 situation
this.setState({ data: data.length === 1 ? data.concat(data) : data }, () => {
this._carousel.snapToItem(data.length, false)
})
}
Hey guys,
I finally had time to dig deeper into the issue. I've tried different things, but I don't see any other solution than the one you've all came up with: to snap without animation to the relevant item when the data set is updated.
With FlatList's specific rendering mechanism, there is no way of keeping track of each item index when adding/removing them; that part has to be handled externally.
Still, if you have another idea of dealing with it, do not hesitate to share it! I'll gladly try to implement it ;-)
Not sure why I did not think of this earlier, but I think we just need to pass a unique key with each item in the list. When the carousel receives new props, it can simply check which keys have disappeared in order to adjust its index automatically.
Please correct me if I am wrong.
@Gerharddc Well, that's an idea worth pursuing. I'll see if I can implement something along those lines.
If anyone wants to jump in and submit a PR, do not hesitate ;-)
Ran into this problem as well.
I gave it some thought and I came up with this:
const predicate = '??';
const prevIndex = prevProps.data.findIndex((value, index) => value === predicate);
const nextIndex = this.props.data.findIndex((value, index) => value === predicate);
const delta = nextIndex - prevIndex;
this._activeItem += delta;
I think the right place in the code would be here.
I tested a simple static implementation (adding one child) => it worked:
if (this._previousItemsLength !== itemsLength) {
nextActiveItem = this._previousItemsLength < itemsLength ? nextActiveItem + 1 : nextActiveItem;
this._hackActiveSlideAnimation(nextActiveItem, null, true, false);
}
However this is far from a viable solution.
Implementing keys as suggested by @Gerharddc would solve the predicate value. and would make life easier for everyone, so it sounds.
I tried testing a better implementation of this but ran into a VERY strange bug. When state/props update after mount prevProps === this.props. I have no clue why this is happening. And have no idea how to work around it.
While testing I ran into a bug in the code (I think), that overrides the _hackActiveSlideAnimation method.
I'll submit a PR.
Most helpful comment
Hi @m-vdb,
I didn't have time to work on this issue lately, but if the problem is spreading I'll try to take a look at it shortly. Not before next week though, since I currently have major professional deadlines to meet ;-)