So I have this basic todo:
App.jsx
const ITEMS = [
{ id: 1, text: 'Example 1' },
{ id: 2, text: 'Example 2' },
{ id: 3, text: 'Example 3' }
];
export const App = () => {
const todoRefs = useSharedValue<Record<string, any>>({});
const closeOpenTodos = (id: number) => {
'worklet';
for (const key in todoRefs.value) {
if (Number(key) !== id) {
todoRefs.value[key].closeTodo();
}
}
};
return (
<ScrollView contentContainerStyle={styles.container}>
{ITEMS.map((item, index) => (
<Item key={index} {...{ ...item, todoRefs, closeOpenTodos }} />
))}
</ScrollView>
);
};
Item.jsx
(i.e. todo)
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const BUTTON_WIDTH = 100;
const CONTAINER_HEIGHT = 80;
const TRANSLATE_X_THRESHOLD = -SCREEN_WIDTH * 0.7;
type ItemProps = {
id: number;
text: string;
todoRefs: Record<string, any>;
closeOpenTodos: (id: number) => void;
};
type ItemContext = {
translateX: number;
};
export const Item = ({ id, text, todoRefs, closeOpenTodos }: ItemProps) => {
const translateX = useSharedValue(0);
const containerHeight = useSharedValue(CONTAINER_HEIGHT);
const dismissItem = () => {
'worklet';
containerHeight.value = withTiming(0, { duration: 100 });
translateX.value = -SCREEN_WIDTH;
};
todoRefs.value = {
...todoRefs.value,
[id]: {
closeTodo: () => {
'worklet';
translateX.value = withTiming(0);
}
}
};
const panGestureEvent = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
ItemContext
>({
onStart: (_event, context) => {
closeOpenTodos(id);
context.translateX = translateX.value;
},
onActive: (event, context) => {
// Prevent swiping to the right
if (event.translationX > 0) {
translateX.value = 0;
return;
}
translateX.value = event.translationX + context.translateX;
},
onEnd: (event, context) => {
// If swiping to the right, close item
if (event.translationX > 0) {
translateX.value = 0;
return;
}
if (event.translationX + context.translateX < TRANSLATE_X_THRESHOLD) {
dismissItem();
return;
}
translateX.value = withSpring(-BUTTON_WIDTH);
}
});
const animatedSliderStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: translateX.value }]
};
}, []);
const animatedContainerStyle = useAnimatedStyle(() => {
return {
height: containerHeight.value
};
}, []);
return (
<Animated.View style={[styles.container, animatedContainerStyle]}>
<Pressable style={[styles.button]} onPress={() => dismissItem()}>
<Text style={styles.buttonText}>Delete</Text>
</Pressable>
<PanGestureHandler onGestureEvent={panGestureEvent}>
<Animated.View style={[styles.slider, animatedSliderStyle]}>
<Text style={styles.sliderText}>{text}</Text>
</Animated.View>
</PanGestureHandler>
</Animated.View>
);
};
Basically when a todo is swiped open, it reveals a delete button. I want it so that if another todo it swiped open, all others close.
So I'm passing down a todoRefs
(which technically aren't "refs") and closeOpenTodos
.
All works fine, except this approach has introduced a strange bug.
When I go to delete items, the last one keeps re-appearing.
Is there a better way to do this?