0

I am trying to animated a circle to expand and shrink in a loop for a breathing app for people with panic attacks. I am trying to make the text change in time with the animation. breathe in state is the text variable which is shown in the circle - it can either be 'inhale', 'hold' or 'exhale'. The animation makes the circle go from 60% to fullsize in the duration of breatheTimings.in, then it should change to saying 'hold' then pause for breatheTimings.hold, then shrink back to 60% whilst saying 'exhale'.

class BreatheCircle extends React.Component {
  state = {
    animated:new Animated.Value(0.6),
    breathe: 'Inhale',

  }

  componentDidMount(){
    this.state.animated.setValue(0.6)
    this.animateCircle();
  }
  componentWillUnmount(){
    Animated.timing(this.state.animated).stop();
  }
  sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
      if ((new Date().getTime() - start) > milliseconds){
        break;
      }
    }
  }
  animateCircle(){
      this.setState({breathe: 'Inhale'})
      Animated.timing(this.state.animated, {toValue: 1, duration:breatheTimings.in*1000}).start()
      this.sleep(breatheTimings.in*1000)
      this.setState({breathe: 'Hold'})
      this.sleep(breatheTimings.hold*1000)
      this.setState({breathe: 'Exhale'})
      Animated.timing(this.state.animated, {toValue: 0.6, duration:breatheTimings.out*1000}).start()
      this.sleep(breatheTimings.out*1000)
      this.animateCircle()
      }

This isn't working at the moment (testing it with expo) Please could someone give me advice on where to go with this. Thanks

2 Answers2

1

You are calling setState in the same time, JavaScript does not stop and wait for other functions to finish.

I won't rewrite this code, and get into the logic of your question but a way to do this right might be using prevState, maybe inside the sleep function. For example:

sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if (!(new Date().getTime() - start) > milliseconds){
      this.setState(prevState => {
        if (prevState.breathe === "Inhale") {
          prevState.breathe = "hold";
        } else if(prevState.breathe === "hold") {
          prevState.breathe = "Exhale";
        } else {
          prevState.breathe = "Inhale"
        }
        return prevState
      });
    }
  }
}

from React docs:

This form of setState() is also asynchronous, and multiple calls during the same cycle may be batched together.

...

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

Idan
  • 5,405
  • 7
  • 35
  • 52
0

you don't need a sleep function and you can try an Animated.sequence (docs here when writing). And I recommend you to write 'Inhale', 'Exhale' and 'Hold' and play with opacity.

class BreatheCircle extends React.Component {
      state = {
          circleAnimation: new Animated.Value(0.6),
          holdAnimation: new Animated.Value(0),
          inhaleAnimation: new Animated.Value(0),
          exhaleAnimation: new Animated.Value(0),
      }

  breathAnimation = Animated.sequence([
      Animated.timing(this.state.inhaleAnimation, {toValue: 1, duration:100}),
      Animated.timing(this.state.circleAnimation, {toValue: 1, duration:breatheTimings.in*1000}),
      Animated.timing(this.state.inhaleAnimation, {toValue: 0, duration:100}),
      Animated.timing(this.state.holdAnimation, {toValue: 1, duration:100}),
      Animated.timing(this.state.holdAnimation, {toValue: 0, duration:100, , delay: breatheTimings.hold*1000}), //delay for the hold to disappear
      Animated.timing(this.state.exhaleAnimation, {toValue: 1, duration:100}),
      Animated.timing(this.state.circleAnimation, {toValue: 0.6, duration:breatheTimings.out*1000}),
      Animated.timing(this.state.exhaleAnimation, {toValue: 0, duration:100}),
  ])

  componentDidMount(){
    // this.state.animated.setValue(0.6) // state already declare
    this.animateCircle();
  }
  componentWillUnmount(){
    this.breathAnimation.stop();
  }

  animateCircle(){
      this.breathAnimation.start(() => this.animatedCircle())
  }


  //[...]

  <Text style={{opacity: this.holdAnimation}}>Hold<Text>
  <Text style={{opacity: this.inhaleAnimation}}>Inhale<Text>
  <Text style={{opacity: this.exhaleAnimation}}>Exhale<Text>
Fromwood
  • 506
  • 3
  • 6
  • I'm getting a "TypeError: undefined is not an object (evaluating '_this3.state.inhaleAnimation) " – Billy Bromell Oct 09 '18 at 10:44
  • Yes, I made a typo. On the top it's 'state = {' and not 'this.state = {' – Fromwood Oct 09 '18 at 12:54
  • I've now got that working! - thank you! However when I go to the settings page of my app and change the values of breatheTimings, the animation timings do not change with it – Billy Bromell Oct 09 '18 at 19:38