-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
useComposedEventHandler
hook (#5890)
## Summary There have already been some requests for a way to merge our event handlers. Inspired by #5854, I created a hook that can work with any handlers made using `useEvent`. I will happily accept any comments and suggestions regarding typing since I am not so sure about it. https://github.com/software-mansion/react-native-reanimated/assets/77503811/9c295325-28ce-4ec2-8490-1a7431e8fafd The PR can be merged only ater #5845 (it is also based on it). ## Test plan Open `useComposedEventHandler` example from Example app and watch event callbacks in console.
- Loading branch information
1 parent
22927c8
commit 88542d6
Showing
12 changed files
with
619 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* eslint-disable no-unused-expressions */ | ||
/* eslint-disable @typescript-eslint/no-empty-function */ | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import React from 'react'; | ||
import Animated, { | ||
useAnimatedScrollHandler, | ||
useComposedEventHandler, | ||
useEvent, | ||
} from '../..'; | ||
import { Text } from 'react-native'; | ||
import type { ReanimatedEvent } from '../..'; | ||
|
||
function useComposedEventHandlerTest() { | ||
function useComposedEventHandlerTestDifferentHandlers() { | ||
function useCustomScrollHandler( | ||
handler: (event: ReanimatedEvent<object>) => void | ||
) { | ||
return useEvent( | ||
(event) => { | ||
'worklet'; | ||
handler(event); | ||
}, | ||
['onScroll'] | ||
); | ||
} | ||
|
||
const onScrollCustomHandler = useCustomScrollHandler((e) => { | ||
'worklet'; | ||
console.log(`custom scroll handler invoked on ${e.eventName}`); | ||
}); | ||
|
||
const onScrollHandler = useAnimatedScrollHandler((e) => { | ||
console.log(`scroll handler invoked on ${e.eventName}`); | ||
}); | ||
|
||
const composedHandler = useComposedEventHandler([ | ||
onScrollHandler, | ||
onScrollCustomHandler, | ||
]); | ||
|
||
return ( | ||
<> | ||
<Animated.ScrollView onScroll={composedHandler}> | ||
{[...new Array(20)].map((_each, index) => ( | ||
<Text>{index}</Text> | ||
))} | ||
</Animated.ScrollView> | ||
</> | ||
); | ||
} | ||
|
||
function useComposedEventHandlerTestDifferentProps() { | ||
const onDragBeginHandler = useAnimatedScrollHandler({ | ||
onBeginDrag(e) { | ||
'worklet'; | ||
console.log('Drag begin'); | ||
}, | ||
}); | ||
|
||
const onDragEndHandler = useAnimatedScrollHandler({ | ||
onEndDrag(e) { | ||
'worklet'; | ||
console.log('Drag end'); | ||
}, | ||
}); | ||
|
||
const composedHandler1 = useComposedEventHandler([ | ||
onDragBeginHandler, | ||
onDragEndHandler, | ||
]); | ||
|
||
const composedHandler2 = useComposedEventHandler([ | ||
onDragBeginHandler, | ||
onDragEndHandler, | ||
]); | ||
|
||
return ( | ||
<> | ||
<Animated.ScrollView | ||
// This will work well thanks to the way it is filtered in `PropsFilter`. | ||
onMomentumScrollBegin={composedHandler1} | ||
// same as above | ||
onMomentumScrollEnd={composedHandler2}> | ||
{[...new Array(20)].map((_each, index) => ( | ||
<Text>{index}</Text> | ||
))} | ||
</Animated.ScrollView> | ||
</> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import React from 'react'; | ||
import { Text, View, StyleSheet, Button } from 'react-native'; | ||
import Animated, { | ||
useAnimatedScrollHandler, | ||
useComposedEventHandler, | ||
} from 'react-native-reanimated'; | ||
|
||
export default function ComposedHandlerConditionalExample() { | ||
const [toggleFirst, setToggleFirst] = React.useState(true); | ||
const [toggleSecond, setToggleSecond] = React.useState(true); | ||
const [toggleThird, setToggleThird] = React.useState(true); | ||
|
||
const handlerFunc = React.useCallback( | ||
(handlerName: string, eventName: string) => { | ||
'worklet'; | ||
console.log(`${handlerName} handler: ${eventName}`); | ||
}, | ||
[] | ||
); | ||
|
||
const firstHandler = useAnimatedScrollHandler({ | ||
onScroll(e) { | ||
handlerFunc('first', e.eventName); | ||
}, | ||
}); | ||
|
||
const secondHandler = useAnimatedScrollHandler({ | ||
onScroll(e) { | ||
handlerFunc('second', e.eventName); | ||
}, | ||
}); | ||
|
||
const thirdHandler = useAnimatedScrollHandler({ | ||
onScroll(e) { | ||
handlerFunc('third', e.eventName); | ||
}, | ||
}); | ||
|
||
const composedHandler = useComposedEventHandler([ | ||
toggleFirst ? firstHandler : null, | ||
toggleSecond ? secondHandler : null, | ||
toggleThird ? thirdHandler : null, | ||
]); | ||
|
||
return ( | ||
<View style={styles.container}> | ||
<Text style={styles.infoText}>Check console logs!</Text> | ||
<ToggleButton | ||
name={'first'} | ||
isToggled={toggleFirst} | ||
onPressFunc={() => setToggleFirst(!toggleFirst)} | ||
/> | ||
<ToggleButton | ||
name={'second'} | ||
isToggled={toggleSecond} | ||
onPressFunc={() => setToggleSecond(!toggleSecond)} | ||
/> | ||
<ToggleButton | ||
name={'third'} | ||
isToggled={toggleThird} | ||
onPressFunc={() => setToggleThird(!toggleThird)} | ||
/> | ||
<Animated.FlatList | ||
onScroll={composedHandler} | ||
style={styles.list} | ||
data={items} | ||
renderItem={({ item }) => <Item title={item.title} />} | ||
keyExtractor={(item) => `A:${item.title}`} | ||
/> | ||
</View> | ||
); | ||
} | ||
|
||
type ToggleProps = { | ||
name: string; | ||
isToggled: boolean; | ||
onPressFunc: () => void; | ||
}; | ||
const ToggleButton = ({ name, isToggled, onPressFunc }: ToggleProps) => ( | ||
<View style={styles.toggleContainer}> | ||
<View | ||
style={[ | ||
styles.toggleIcon, | ||
isToggled ? styles.toggleON : styles.toggleOFF, | ||
]} | ||
/> | ||
<Button | ||
title={`Toggle ${name} handler ${isToggled ? 'OFF' : 'ON'}`} | ||
onPress={onPressFunc} | ||
/> | ||
</View> | ||
); | ||
|
||
type ItemValue = { title: string }; | ||
const items: ItemValue[] = [...new Array(101)].map((_each, index) => { | ||
return { title: `${index}` }; | ||
}); | ||
|
||
type ItemProps = { title: string }; | ||
const Item = ({ title }: ItemProps) => ( | ||
<View style={styles.item}> | ||
<Text style={styles.title}>{title}</Text> | ||
</View> | ||
); | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
flexDirection: 'column', | ||
}, | ||
infoText: { | ||
fontSize: 19, | ||
alignSelf: 'center', | ||
}, | ||
list: { | ||
flex: 1, | ||
}, | ||
item: { | ||
backgroundColor: '#883ef0', | ||
alignItems: 'center', | ||
padding: 20, | ||
marginVertical: 8, | ||
marginHorizontal: 16, | ||
borderRadius: 20, | ||
}, | ||
title: { | ||
fontSize: 32, | ||
}, | ||
toggleContainer: { | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}, | ||
toggleIcon: { | ||
width: 20, | ||
height: 20, | ||
borderRadius: 10, | ||
borderWidth: 3, | ||
borderColor: 'black', | ||
}, | ||
toggleON: { | ||
backgroundColor: '#7CFC00', | ||
}, | ||
toggleOFF: { | ||
backgroundColor: 'red', | ||
}, | ||
}); |
96 changes: 96 additions & 0 deletions
96
app/src/examples/ComposedHandlerDifferentEventsExample.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import React from 'react'; | ||
import { Text, View, StyleSheet } from 'react-native'; | ||
import Animated, { | ||
useAnimatedScrollHandler, | ||
useComposedEventHandler, | ||
} from 'react-native-reanimated'; | ||
|
||
export default function ComposedHandlerDifferentEventsExample() { | ||
const handlerFunc = React.useCallback( | ||
(handlerName: string, eventName: string) => { | ||
'worklet'; | ||
console.log(`${handlerName} handler: ${eventName}`); | ||
}, | ||
[] | ||
); | ||
|
||
const onScrollHandler = useAnimatedScrollHandler({ | ||
onScroll(e) { | ||
handlerFunc('scroll', e.eventName); | ||
}, | ||
}); | ||
|
||
const onDragHandler = useAnimatedScrollHandler({ | ||
onBeginDrag(e) { | ||
handlerFunc('drag', e.eventName); | ||
}, | ||
onEndDrag(e) { | ||
handlerFunc('drag', e.eventName); | ||
}, | ||
}); | ||
|
||
const onMomentumHandler = useAnimatedScrollHandler({ | ||
onMomentumBegin(e) { | ||
handlerFunc('momentum', e.eventName); | ||
}, | ||
onMomentumEnd(e) { | ||
handlerFunc('momentum', e.eventName); | ||
}, | ||
}); | ||
|
||
const composedHandler = useComposedEventHandler([ | ||
onScrollHandler, | ||
onDragHandler, | ||
onMomentumHandler, | ||
]); | ||
|
||
return ( | ||
<View style={styles.container}> | ||
<Text style={styles.infoText}>Check console logs!</Text> | ||
<Animated.FlatList | ||
onScroll={composedHandler} | ||
style={styles.list} | ||
data={items} | ||
renderItem={({ item }) => <Item title={item.title} />} | ||
keyExtractor={(item) => `A:${item.title}`} | ||
/> | ||
</View> | ||
); | ||
} | ||
|
||
type ItemValue = { title: string }; | ||
const items: ItemValue[] = [...new Array(101)].map((_each, index) => { | ||
return { title: `${index}` }; | ||
}); | ||
|
||
type ItemProps = { title: string }; | ||
const Item = ({ title }: ItemProps) => ( | ||
<View style={styles.item}> | ||
<Text style={styles.title}>{title}</Text> | ||
</View> | ||
); | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
flexDirection: 'column', | ||
}, | ||
infoText: { | ||
fontSize: 19, | ||
alignSelf: 'center', | ||
}, | ||
list: { | ||
flex: 1, | ||
}, | ||
item: { | ||
backgroundColor: '#66e3c0', | ||
alignItems: 'center', | ||
padding: 20, | ||
marginVertical: 8, | ||
marginHorizontal: 16, | ||
borderRadius: 20, | ||
}, | ||
title: { | ||
fontSize: 32, | ||
}, | ||
}); |
Oops, something went wrong.