2

First of all, I know there are a lot of questions on this topic, and I've read through all the ones that I thought applied to my situation. This thread React component initialize state from props in particular seemed to be what I needed, but nothing mentioned here worked. Anyway, onto my code. It's just a simple countdown timer that takes in user input (in minutes) for its starting point:

class Timer extends Component {
    constructor(props) {
        super(props);
        this.state = {
            minutes: props.workPeriod,
            seconds: 0
        };
      }
    componentDidMount() {  
        setInterval(() => {
            const {minutes, seconds} = this.state
            console.log("minute state: ", minutes)
            if(this.props.countdownHasStarted) {
                if(seconds > 0) {
                    this.setState(({seconds}) => ({
                        seconds: seconds - 1
                    }))
                }
                if(seconds === 0) {
                    if(minutes === 0) {
                        clearInterval(this.myInterval)
                    } else {
                        this.setState(({minutes}) => ({
                            minutes: minutes - 1,
                            seconds: 59
                        }))
                    }
                }
            }
        }, 1000)
    }

...

const selector = formValueSelector('intervalSettings')
Timer = connect(state => {
    const workPeriod = selector(state, 'workPeriod')
    return {
        workPeriod,
        countdownHasStarted: state.countdownHasStarted
    }
})(Timer)

Due to where everything's located on the component tree, I used Redux, so workPeriod comes from the Redux store, if that makes any difference. When I print out 'minutes' in the console, I get undefined, and sure enough when it's rendered, I just get NaN for the minutes. How do I get props.workPeriod into state so that it's defined and able to be manipulated?

I included how I got workPeriod from the Redux store just in case my woes have something to do with that, but {this.props.workPeriod} renders just fine, so I assume everything's good there.

Thanks in advance!

(edited to incorporate previous suggestions and questions)

Bassem
  • 3,582
  • 2
  • 24
  • 47
Benjamin
  • 55
  • 8

2 Answers2

3

This is happening because the redux store is initialized with an empty object. By the time the redux-form reducer initializes its own initialValues, the <Timer /> component gets an undefined value of workPeriod and kicks-off the setInterval(). Here is how I would solve this issue using React Hooks:

import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { formValueSelector } from "redux-form";

let Timer = ({ workPeriod, countdownHasStarted }) => {
  const [seconds, setSeconds] = useState(0);
  const [minutes, setMinutes] = useState();

  useEffect(() => {
    // first initialize the minutes when the workPeriod is initialized
    setMinutes(workPeriod);
  }, [workPeriod]);

  useEffect(() => {
    let interval;
    if (minutes) {
      interval = setInterval(() => {
        console.log("minute state: ", minutes);
        if (countdownHasStarted) {
          if (seconds > 0) {
            setSeconds((sec) => sec - 1);
          }
          if (seconds === 0) {
            if (minutes === 0) {
              clearInterval(interval);
            } else {
              setMinutes((min) => min - 1);
              setSeconds(59);
            }
          }
        }
      }, 1000);
    }

    return () => {
      // cleanup function
      clearInterval(interval);
    };
  }, [countdownHasStarted, minutes, seconds]);

  return (
    <div className="numbers">
      <div className="box" id="minutes">
        <p>{minutes}</p>
      </div>
      <div className="box" id="colon">
        <p>:</p>
      </div>
      <div className="box" id="seconds">
        <p>{seconds < 10 ? `0${seconds}` : seconds}</p>
      </div>
    </div>
  );
};

Timer = connect((state) => {
  const selector = formValueSelector("intervalSettings");
  const workPeriod = selector(state, "workPeriod");
  return {
    workPeriod,
    countdownHasStarted: state.countdownHasStarted,
  };
})(Timer);

export default Timer;
Bassem
  • 3,582
  • 2
  • 24
  • 47
  • This did it! I was using React Hooks at first, but then I ran into issues with conditional rendering, so I changed to a class-based component. But I've since simplified the rendering issue, so this works perfectly. – Benjamin May 14 '20 at 02:28
1

const {minutes, seconds} = this.setState

did you mean

const {minutes, seconds} = this.state?

Kevin Wang
  • 2,673
  • 2
  • 10
  • 18
  • I actually had it as `this.state` in an earlier iteration, and I changed it to `this.setState` just to see what would happen, and made no difference, so I just kept it. I changed it back to `this.state` just now just to make sure, and it still doesn't make a difference I can notice. – Benjamin May 13 '20 at 23:40
  • ok, unless there's some hidden behavior I don't know about, `const {...} = this.setState` will not work, because `setState` is a function. `this.state` is the object that actually contains the state variables. – Kevin Wang May 13 '20 at 23:44