Skip to content
This repository was archived by the owner on Aug 13, 2024. It is now read-only.

Commit 3c7f814

Browse files
authored
feat: implemented onStartReached (#1)
* chore: remove link * chore: add base helpers * chore: implement basic query * chore: added basic implementation * feat: implement bi-direction * chore: renaming more clearly * skip * test: added deferred test * chore: remove unused useCallback
1 parent b928eb2 commit 3c7f814

File tree

11 files changed

+373
-143
lines changed

11 files changed

+373
-143
lines changed

example/ios/Podfile.lock

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,6 @@ PODS:
329329
- React-jsinspector (0.71.5)
330330
- React-logger (0.71.5):
331331
- glog
332-
- react-native-scrollview-enhancer (0.1.0):
333-
- React-Core
334332
- React-perflogger (0.71.5)
335333
- React-RCTActionSheet (0.71.5):
336334
- React-Core/RCTActionSheetHeaders (= 0.71.5)
@@ -466,7 +464,6 @@ DEPENDENCIES:
466464
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
467465
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
468466
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
469-
- react-native-scrollview-enhancer (from `../..`)
470467
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
471468
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
472469
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
@@ -541,8 +538,6 @@ EXTERNAL SOURCES:
541538
:path: "../node_modules/react-native/ReactCommon/jsinspector"
542539
React-logger:
543540
:path: "../node_modules/react-native/ReactCommon/logger"
544-
react-native-scrollview-enhancer:
545-
:path: "../.."
546541
React-perflogger:
547542
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
548543
React-RCTActionSheet:
@@ -606,7 +601,6 @@ SPEC CHECKSUMS:
606601
React-jsiexecutor: 1579bf3207afadc72ac3638a66a102d1bf5263e3
607602
React-jsinspector: 14a342151ab810862998dfc99e2720746734e9b3
608603
React-logger: 94ec392ae471683635e4bf874d4e82f675399d2d
609-
react-native-scrollview-enhancer: 5139533f7c2f5772cc91aa2da940d5cc01b32175
610604
React-perflogger: 883a55a9a899535eaf06d0029108ef9ef22cce92
611605
React-RCTActionSheet: 1a3b8416688a3d291367be645022886f71d6842a
612606
React-RCTAnimation: e5560cb72d91ba35151d51e2eb0d467b42763f43

example/metro.config.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ module.exports = {
1616
// We need to make sure that only one version is loaded for peerDependencies
1717
// So we block them at the root, and alias them to the versions in example's node_modules
1818
resolver: {
19-
blacklistRE: exclusionList(
20-
modules.map(
21-
(m) =>
22-
new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)
23-
)
24-
),
19+
blacklistRE: exclusionList(modules.map((m) => new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`))),
2520

2621
extraNodeModules: modules.reduce((acc, name) => {
2722
acc[name] = path.join(__dirname, 'node_modules', name);

example/src/App.tsx

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,85 @@
11
import * as React from 'react';
2-
import { useState } from 'react';
2+
import { useRef, useState } from 'react';
33

4-
import { Pressable, StyleSheet, Text, View } from 'react-native';
5-
import {
6-
FlatList,
7-
ScrollView,
8-
} from '@sendbird/react-native-scrollview-enhancer';
4+
import { FlatList as RNFlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
5+
import { FlatList } from '@sendbird/react-native-scrollview-enhancer';
96

10-
let lastIdx = 0;
117
function messageFetcher(count: number) {
12-
const response = new Array(count)
8+
return new Array(count)
139
.fill(0)
1410
.map((_, index) => ({
1511
id: `${index}+${Date.now()}`,
16-
message: `Message ${lastIdx + index}`,
12+
message: `Message ${index}`,
1713
}))
1814
.reverse();
19-
lastIdx += count;
20-
return response;
15+
}
16+
17+
class BasicQuery {
18+
private _messages = messageFetcher(500).reverse();
19+
private _cursor = {
20+
prev: this._messages.length / 2,
21+
next: this._messages.length / 2 - 1,
22+
};
23+
24+
loadPrev(count: number) {
25+
const sliceRange = [this._cursor.prev, this._cursor.prev + count];
26+
this._cursor.prev += count;
27+
return this._messages.slice(...sliceRange);
28+
}
29+
loadNext(count: number) {
30+
const sliceRange = [this._cursor.next - count, this._cursor.next];
31+
this._cursor.next -= count;
32+
return this._messages.slice(...sliceRange);
33+
}
34+
get hasNext() {
35+
return this._cursor.next > 0;
36+
}
37+
get hasPrev() {
38+
return this._cursor.prev < this._messages.length;
39+
}
2140
}
2241

2342
export default function App() {
24-
const [layout, setLayout] = useState(50);
25-
const [messages, setMessages] = useState<{ id: string; message: string }[]>(
26-
() => messageFetcher(5)
27-
);
43+
const query = useRef(new BasicQuery());
44+
const [messages, setMessages] = useState<{ id: string; message: string }[]>(() => query.current.loadPrev(10));
45+
46+
const viewRef = useRef<RNFlatList>(null);
47+
48+
const onEndReached = () => {
49+
console.log('onEndReached');
50+
setMessages((prev) => [...prev, ...query.current.loadPrev(5)]);
51+
};
52+
53+
const onStartReached = () => {
54+
console.log('onStartReached');
55+
setMessages((prev) => [...query.current.loadNext(5), ...prev]);
56+
};
2857

2958
return (
3059
<View style={styles.container}>
31-
<ScrollView>
32-
<Text>{'123'}</Text>
33-
</ScrollView>
3460
<FlatList
3561
inverted
62+
ref={viewRef}
3663
style={styles.box}
37-
onStartReachedThreshold={2}
38-
maintainVisibleContentPosition={{
39-
autoscrollToTopThreshold: 0,
40-
minIndexForVisible: 1,
41-
}}
42-
onStartReached={() => {
43-
setMessages((prev) => [...prev, ...messageFetcher(20)]);
44-
}}
45-
onEndReached={() => {
46-
setMessages((prev) => [...messageFetcher(20), ...prev]);
47-
}}
48-
onLayout={(e) => console.log('flatlist', e.nativeEvent)}
64+
onEndReached={onEndReached}
65+
onStartReached={onStartReached}
4966
data={messages}
5067
keyExtractor={(item) => item.id}
5168
renderItem={({ item }) => {
5269
return (
53-
<View
54-
style={{
55-
width: '100%',
56-
backgroundColor: 'gray',
57-
marginBottom: 4,
58-
padding: 24,
59-
}}
60-
>
61-
<Text style={{ color: 'white' }}>{item.message}</Text>
70+
<View style={styles.message}>
71+
<Text style={styles.messageText}>{item.message}</Text>
6272
</View>
6373
);
6474
}}
6575
/>
66-
<Pressable
67-
style={{ width: 100, height: 100 }}
68-
onPress={() => {
69-
setMessages((prev) => [...messageFetcher(20), ...prev]);
70-
}}
71-
>
72-
<Text>{'Load more'}</Text>
73-
</Pressable>
74-
75-
<View style={{ width: layout, height: 20, backgroundColor: 'blue' }}>
76-
<Pressable onPress={() => setLayout((prev) => (prev === 25 ? 50 : 25))}>
77-
<Text>{'Change'}</Text>
78-
</Pressable>
76+
<View style={styles.buttons}>
77+
<TouchableOpacity style={styles.button} onPress={() => viewRef.current?.scrollToEnd()}>
78+
<Text>{'scroll to end'}</Text>
79+
</TouchableOpacity>
80+
<TouchableOpacity style={styles.button} onPress={() => viewRef.current?.scrollToOffset({ offset: 0 })}>
81+
<Text>{'scroll to start'}</Text>
82+
</TouchableOpacity>
7983
</View>
8084
</View>
8185
);
@@ -86,6 +90,28 @@ const styles = StyleSheet.create({
8690
flex: 1,
8791
},
8892
box: {
93+
backgroundColor: 'black',
94+
flex: 1,
95+
},
96+
message: {
97+
width: '100%',
98+
backgroundColor: 'gray',
99+
marginBottom: 4,
100+
padding: 24,
101+
},
102+
messageText: {
103+
fontWeight: 'bold',
104+
color: 'white',
105+
},
106+
buttons: {
107+
flexDirection: 'row',
108+
},
109+
button: {
89110
flex: 1,
111+
height: 80,
112+
margin: 2,
113+
alignItems: 'center',
114+
justifyContent: 'center',
115+
backgroundColor: '#1b77ff',
90116
},
91117
});

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
"registry": "https://registry.npmjs.org/"
5353
},
5454
"devDependencies": {
55-
"@evilmartians/lefthook": "^1.2.2",
5655
"@commitlint/config-conventional": "^17.0.2",
56+
"@evilmartians/lefthook": "^1.2.2",
5757
"@react-native-community/eslint-config": "^3.0.2",
5858
"@release-it/conventional-changelog": "^5.0.0",
5959
"@types/jest": "^28.1.2",
@@ -83,7 +83,6 @@
8383
"engines": {
8484
"node": ">= 16.0.0"
8585
},
86-
"packageManager": "^[email protected]",
8786
"jest": {
8887
"preset": "react-native",
8988
"modulePathIgnorePatterns": [
@@ -123,6 +122,7 @@
123122
"prettier/prettier": [
124123
"error",
125124
{
125+
"printWidth": 120,
126126
"quoteProps": "consistent",
127127
"singleQuote": true,
128128
"tabWidth": 2,
@@ -137,6 +137,7 @@
137137
"lib/"
138138
],
139139
"prettier": {
140+
"printWidth": 120,
140141
"quoteProps": "consistent",
141142
"singleQuote": true,
142143
"tabWidth": 2,

src/__tests__/deferred.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { deferred } from '../deferred';
2+
3+
describe('deferred', () => {
4+
it('resolves with correct value', async () => {
5+
const deferredString = deferred<string>();
6+
const expectedString = 'hello world';
7+
setTimeout(() => {
8+
deferredString.resolve(expectedString);
9+
}, 1000);
10+
const resultString = await deferredString.promise;
11+
expect(resultString).toBe(expectedString);
12+
});
13+
14+
it('rejects with correct reason', async () => {
15+
const deferredError = deferred<Error>();
16+
const expectedError = new Error('Something went wrong');
17+
setTimeout(() => {
18+
deferredError.reject(expectedError);
19+
}, 1000);
20+
try {
21+
await deferredError.promise;
22+
} catch (error) {
23+
expect(error).toBe(expectedError);
24+
}
25+
});
26+
});

src/deferred.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
interface Deferred<T> {
2+
promise: Promise<T>;
3+
resolve: (value?: T | PromiseLike<T>) => void;
4+
reject: (reason?: any) => void;
5+
}
6+
7+
export const deferred = <T>(): Deferred<T> => {
8+
let resolve!: (value: T | PromiseLike<T>) => void;
9+
let reject!: (reason?: any) => void;
10+
11+
const promise = new Promise<T>((res, rej) => {
12+
resolve = res;
13+
reject = rej;
14+
});
15+
16+
return { promise, resolve, reject } as Deferred<T>;
17+
};

0 commit comments

Comments
 (0)