Skip to content

Commit d1fb606

Browse files
authored
fix: enabled prop behavior for KeyboardStickyView (fabric arch) (#1312)
## 📜 Description Fixed `enabled` behavior for `KeyboardStickyView` on fabric. ## 💡 Motivation and Context It looks like on fabric when we detach animated value we "break" graph of native driver animations (sine we don't use Animated value anymore) and animations becomes just frozen (element is not getting moved to its initial position). To overcome this problem I replaced plain `closed` value with `Animated` one: ```ts Animated.add(Animated.multiply(height, 0), closed); ``` Effectively we still consume `height`, but we multiply it by `0` (we get `0` after that) and after that we make additions `0` + `closed` as animated value. As a result we always switch between animated values and we always use `height` value (it was critical, if we stop to use it we get again the same bug). I used this code for testing: ```tsx import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Button, Text, View } from "react-native"; import { KeyboardAwareScrollView, KeyboardController, KeyboardStickyView, } from "react-native-keyboard-controller"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import TextInput from "../../../components/TextInput"; import { styles } from "./styles"; import type { ExamplesStackParamList } from "../../../navigation/ExamplesStack"; import type { StackScreenProps } from "@react-navigation/stack"; import type { LayoutChangeEvent } from "react-native"; type Props = StackScreenProps<ExamplesStackParamList>; const variants = ["v1", "v2", "v3"] as const; type Variant = (typeof variants)[number]; export default function AwareScrollViewStickyFooter({ navigation }: Props) { const { bottom } = useSafeAreaInsets(); const [footerHeight, setFooterHeight] = useState(0); const [additionalHeight, setAdditionalHeight] = useState(0); const [variant, setVariant] = useState<Variant>("v1"); const handleLayout = useCallback((evt: LayoutChangeEvent) => { setFooterHeight(evt.nativeEvent.layout.height); }, []); const offset = useMemo( () => ({ closed: 0, opened: variant === "v1" ? 0 : bottom }), [bottom, variant], ); const offsetV3 = useMemo( () => ({ closed: -50, opened: bottom - 25 }), [bottom], ); const [enabled, setEnabled] = useState(true); useEffect(() => { navigation.setOptions({ headerRight: () => ( <Text style={styles.header} onPress={() => { const index = variants.indexOf(variant); setVariant(variants[index === variants.length - 1 ? 0 : index + 1]); }} > {variant} </Text> ), }); }, [variant]); const v1v2 = variant === "v1" || variant === "v2"; return ( <View style={[ styles.pageContainer, { paddingBottom: variant === "v1" ? 0 : bottom }, ]} > <KeyboardAwareScrollView bottomOffset={(v1v2 ? footerHeight + additionalHeight : 0) + 50} contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled" style={styles.container} testID="aware_scroll_sticky_view_scroll_container" > {new Array(10).fill(0).map((_, i) => ( <TextInput key={i} keyboardType={i % 2 === 0 ? "numeric" : "default"} placeholder={`TextInput#${i}`} /> ))} </KeyboardAwareScrollView> {v1v2 && ( <KeyboardStickyView offset={offset} enabled={enabled}> <View style={{ height: additionalHeight, backgroundColor: "magenta" }} /> <View style={styles.footer} onLayout={handleLayout}> <Text style={styles.footerText}>A mocked sticky footer</Text> <TextInput placeholder="Amount" style={styles.inputInFooter} /> <Button testID="toggle_height" title="Toggle height" onPress={() => { setEnabled(false); setTimeout(() => { KeyboardController.dismiss(); }, 500) }} /> </View> </KeyboardStickyView> )} {variant === "v3" && ( <KeyboardStickyView offset={offsetV3}> <View style={styles.circle} /> </KeyboardStickyView> )} </View> ); } ``` ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - don't break animated graph; ## 🤔 How Has This Been Tested? Tested in example project manually. ## 📸 Screenshots (if appropriate): |Before|After| |-------|-----| |<video src="https://github.com/user-attachments/assets/6adbf44a-4256-4e1f-b842-d94284b1c8d9">|<video src="https://github.com/user-attachments/assets/6026bd1b-c643-4be0-b331-ca0c1006e53f">| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent 76056c3 commit d1fb606

1 file changed

Lines changed: 10 additions & 6 deletions

File tree

src/components/KeyboardStickyView/index.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,21 @@ const KeyboardStickyView = forwardRef<
5858
outputRange: [closed, opened],
5959
});
6060

61-
const styles = useMemo(
62-
() => [
61+
const styles = useMemo(() => {
62+
const disabled = Animated.add(Animated.multiply(height, 0), closed);
63+
const active = Animated.add(height, offset);
64+
65+
return [
6366
{
6467
transform: [
65-
{ translateY: enabled ? Animated.add(height, offset) : closed },
68+
{
69+
translateY: enabled ? active : disabled,
70+
},
6671
],
6772
},
6873
style,
69-
],
70-
[closed, enabled, height, offset, style],
71-
);
74+
];
75+
}, [closed, enabled, height, offset, style]);
7276

7377
return (
7478
<Animated.View ref={ref} style={styles} {...props}>

0 commit comments

Comments
 (0)