Search code examples
reactjsreduxredux-formreact-propsreact-state

Can't initialize state from props


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)


Solution

  • 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;