0

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?

enter image description here

Nathan
  • 7,627
  • 11
  • 46
  • 80

0 Answers0