Home > Software engineering >  How to hide component after delay in React Native
How to hide component after delay in React Native

Time:11-14

I'm trying to make a chronometer component who hides himself after a delay. It works but I have a warning when the Chronometer disappear and I don't know how to deal with it.

Warning: Cannot update a component (WorkoutScreen) while rendering a different component (Chronometer). To locate the bad setState() call inside Chronometer

WorkoutScreen.tsx

const WorkoutScreen = ({
  navigation,
  route,
}: RootStackScreenProps<"Workout">) => {
  const [inRest, setInRest] = useState(false)
  const [restTime, setRestTime] = useState(5)

  //I pass it to child
  const handleEndRestTime = () => {
    setInRest(false)
  }
  //

  return (
    <Layout style={styles.container}>
      <Button
        onPress={() => {
          setInRest(!inRest)
        }}
      >
        Trigger chronometer
      </Button>
      {inRest && (
        <Chronometer onEnd={handleEndRestTime} seconds={restTime}></Chronometer>
      )}
    </Layout>
  )
}

Chronometer.tsx

const Chronometer = ({ seconds, onEnd }: Props) => {
  const [timer, setTimer] = useState<number>(seconds)
  const [pause, setPause] = useState(false)
  const [running, setRunning] = useState(true)

  useEffect(() => {
    let interval: NodeJS.Timer
    if (pause === true || running === false) {
      ;() => clearInterval(interval)
    } else {
      interval = setInterval(() => {
        setTimer((timer) => timer - 1)
      }, 1000)
    }
    return () => {
      clearInterval(interval)
    }
  }, [pause, running])

  if (timer === 0 && running === true) {
    setRunning(false)
    //From parent
    onEnd()
    //
  }

  return (
    <View style={styles.container}>
      <View style={styles.chronometer}>
        <View style={styles.controls}>
          <Text>{formatHhMmSs(timer)}</Text>
        </View>
        <Button
          onPress={() => {
            setPause(!pause)
          }}
        >
          Pause
        </Button>
      </View>
    </View>
  )
}

When I remove the "{inRest && " the warning disappear.

In the future I want that the User can retrigger Chronometer as he want

Thanks in advance !

Warning on my emulator (1)

Warning on my emulator (2)

Warning on my emulator (3)

Warning on my terminal

CodePudding user response:

There are two state updates that happen simultaneously and conflict with React rendering UI reconciliation

  1. setRunning(false) inside the Chronometer component will rerender this component when the timer ends.

  2. setInRest(false) inside WorkoutScreen component will also rerender when the timer ends.

Both those rerenders happen at the same timer and WorkoutScreen rerender is triggered by the child component.

The solution is to avoid triggering state change inside the parent component caused by the child component.

const WorkoutScreen = ({
  navigation,
  route,
}: RootStackScreenProps<"Workout">) => {
  const [restTime, setRestTime] = useState(5);

  //I pass it to child
  const handleEndRestTime = () => {
    // Handle logic when workout time end
  };
  //

  return (
    <Layout style={styles.container}>
      <Chronometer onEnd={handleEndRestTime} seconds={restTime}></Chronometer>
    </Layout>
  );
};

const Chronometer = ({ seconds, onEnd }: Props) => {
  const [timer, setTimer] = useState < number > seconds;
  const [pause, setPause] = useState(false);
  const [running, setRunning] = useState(true);
  const [inRest, setInRest] = useState(false);

  useEffect(() => {
    let interval: NodeJS.Timer;
    if (pause === true || running === false) {
      () => clearInterval(interval);
    } else {
      interval = setInterval(() => {
        setTimer((timer) => timer - 1);
      }, 1000);
    }
    return () => {
      clearInterval(interval);
    };
  }, [pause, running]);

  if (timer === 0 && running === true) {
    setRunning(false);
    setInRest(false);
    //From parent
    onEnd();
    //
  }

  return (
    <View style={styles.container}>
      <Button
        onPress={() => {
          setInRest(!inRest);
        }}
      >
        Trigger chronometer
      </Button>

      {inRest && (
        <View style={styles.chronometer}>
          {/* Show Timer counter only when is running */}
          {running && (
            <View style={styles.controls}>
              <Text>{formatHhMmSs(timer)}</Text>
            </View>
          )}

          <Button
            onPress={() => {
              setPause(!pause);
            }}
          >
            {running ? "Pause" : "Start"}
          </Button>
        </View>
      )}
    </View>
  );
};

  • Related