vydimitrov / react-countdown-circle-timer

Lightweight React/React Native countdown timer component with color and progress animation based on SVG
MIT License
692 stars 87 forks source link

Warning: Cannot update a component from inside the function body of a different component #186

Closed WagdySamih closed 2 years ago

WagdySamih commented 2 years ago

Warning: Cannot update a component from inside the function body of a different component

I am trying to use react-countdown-circle-timer but I am facing an error

 Warning: Cannot update a component from inside the function body of a different component.
    in p (at CountdownCircleTimer.jsx:93)
    in RCTView (at View.js:34)
    in View (at CountdownCircleTimer.jsx:59)
    in CountdownCircleTimer (at TimeCounter.js:22)
    in RCTView (at View.js:34)

here is my code

const TimeCounter = ({
  color,
  startTime,
  onCompleteHandler,
  onTimeChangeHandler,
  setTimeLeft,
}) => {
  let timeLeft;
  useEffect(() => {
    setTimeLeft && setTimeLeft(timeLeft);
  }, []);

  return (
    <ImageBackground
      style={styles.timerBackground}
      source={require("assets/Icons/online-provider/call-request.png")}
    >
      <CountdownCircleTimer
        onComplete={onCompleteHandler}
        isPlaying
        duration={startTime}
        initialRemainingTime={startTime}
        trailColor="transparent"
        rotation="counterclockwise"
        colors={color}
      >
        {({ remainingTime }) => {
          onTimeChangeHandler(remainingTime);
          setTimeLeft && setTimeLeft(remainingTime);
          return <Text style={styles.seconds}>{remainingTime}</Text>;
        }}
      </CountdownCircleTimer>
    </ImageBackground>
  );
};

and Iam calling this component from here


import React, { useEffect } from "react";
import {
  Text,
  View,
  ImageBackground,
} from "react-native";
import { mainColor, darkBlue } from "constants/Colors";
import TimeCounter from "../../components/TimeCounter";

const ServiceRequestAccepted = () => {
  const onTimeChangeHandler = (remainingTime) => {
      // do some stuff with remaining time
  };
  return (
    <ImageBackground
      style={styles.background}
      source={require("assets/Icons/Common/background.png")}
    >
      <View style={styles.container}>
        <View style={styles.titleContainer}>
          <Text style={styles.title}>Waiting Provider Response</Text>
        </View>
        <TimeCounter
          color={mainColor}
          startTime={30}
          onCompleteHandler={() => {
            // navigate to another screen
            return [true, 1500]; // repeat animation in 1.5 seconds
          }}
          onTimeChangeHandler={onTimeChangeHandler}
        />
      </View>
    </ImageBackground>
  );
};

export default ServiceRequestAccepted
vydimitrov commented 2 years ago

Hey, can you try to remove onTimeChangeHandler(remainingTime); and see if the fixes the warning?

vydimitrov commented 2 years ago

From what I am getting from the error it seems you are trying to call a setState on the parent component from the child component/CountdownCircleTimer function body. Try to keep the state local to the TimeCounter component and see if that will happen again.

WagdySamih commented 2 years ago

@vydimitrov That's true But what if I want to use the remainingTime value to dispatch another action after a specific time? Is this behavior can be done using renderAriaTime function?

vydimitrov commented 2 years ago

I'd recommend using useRef hook instead of useState if you want to keep a reference to the remaining time. The ref hook will not cause rerendering and you will not get into this problem. You can fire your async actions from the children function in the CountdownTime body as you have it.

ucheNkadiCode commented 2 years ago

@vydimitrov, from the point where I would use the useRef hook, how do you recommend that I still manage to update the state of another component? In the case of my app, I take the remainingTime, and multiply it by how many laps the user has remaining and how long they think a lap should take.

There is a Text element outside of the Countdown timer that is "Estimated time: {estimatedTime}". I basically need to be able to update this value every time the timeRemainingRef gets updated. estimatedTime = timeRemainingRef.current + (NumberOfSets * TimeOfSet)

Do you have any tips for this similar issue?

Update:

I was able to find the answer after a lot of searching on stack overflow. Basically if you want to use a setState, wrap it in a settimeout function like so

const [estimatedTime, setEstimatedTime] = useState(60);

<CountdownCircleTimer
...

{({ remainingTime }) => {
setTimeout(() => {setEstimatedTime(remainingTime)
}, 0);
return <Text style={styles.seconds}>{remainingTime}</Text>;
}}
</CountdownCircleTimer>

This basically makes the function call synchronous and React actually lets it slide. Thank you for making such an awesome component! It's helped me so much! https://stackoverflow.com/a/67414049/17172147

vydimitrov commented 2 years ago

I will be adding a new prop onUpdate with the next major version (I am already working on it) of the component that should help here. I am closing this bug for now.