Search code examples
reactjsreact-nativereact-propsreact-functional-componentlong-press

Longpress multipress not rerendering outer counter correctly


I want the button to increment the number by one on every normal press and keep incrementing when longpressing the button, until it is released.

I am using the TouchableOpacity longpress property to start a setInterval, and it looks like the outer props is called correctly. However, the outer Counter component does update the state internally but we don't see any updates rendered to the screen. Only when the button is released and pressed again (once) we can see that it counted in disguise.

Why is this happening? Is this an issue of the longpress property?

Here is the Snack: https://snack.expo.io/xXP_PiqIy

Here is the code:

import React, { useRef, useState } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';

export default function App() {
  const [count, setCount]  = useState(10)

  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
    <Text>{count}</Text>
      <CounterButton onPress={(count) => setCount(count)} initialCount={10} />
    </View>
  );
}

const CounterButton = (props) => {
  const [count, setCount] = useState(props.initialCount ?? 0);

  const onPress = () => {
    props.onPress(count + 1);
    setCount((count) => count + 1);
  };

  return <MultiTouchable onPress={onPress} text={' + '} />;
};

const MultiTouchable = (props) => {
  const isPressing = useRef(false);
  const timer = useRef();

  const onPress = () => {
    props.onPress();
  };

  const onLongPress = () => {
    isPressing.current = true;
    timer.current = setInterval(onPress, 200);
  };

  const cancelLongPress = () => {
    if (isPressing.current) {
      clearInterval(timer.current);
      isPressing.current = false;
    }
  };

  return (
    <TouchableOpacity
      style={{ width: 50, height: 50, justifyContent: "center", alignItems: "center", backgroundColor: 'lightblue' }}
      onPressIn={onPress}
      onLongPress={onLongPress}
      onPressOut={cancelLongPress}>
      <Text>{props.text}</Text>
    </TouchableOpacity>
  );
};

Solution

  • Looks like the issue is not the longpress at all.

    I need to update the counter and also call the props.onPress inside the setCounter callback, like so

    If someone can explain in a comment why this needs to be done here I would be really happy.

    // CounterButton
    
    // ❌ doesn't work
    const onPress = () => {
      props.onPress(count + 1);
      setCount((count) => count + 1);
    };
    
    // ✅ works
    const onPress = () => {
      setCount((count) => {
        onPress(count + 1);
        return count + 1;
      });
    };