Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Build a timer (setInterval & clearInterval) using Redux Middleware Thunk

I am trying to build a timer with a button, click to start, and stop the timer. I have been trying different ways and reading StackOverflow past questions, but still can not make it work on my application. I have already set up the applyMiddleware(thunk) to the store.

Basically I have a button like

      <button onClick={props.timerHandler}>
        {props.isRunning ? "TIMER STOP" : "TIMER START"}
      </button>

Theprops.timerHandler is to change the isRunning value to true and false. In reducer funtion, I have tried something like this, but it is not working

    case "TIMER":
      let timer = null;
      clearInterval(timer);
      if (state.isRunning === true)
        timer = setInterval(() => {
          return state + 1;
        }, 1000);
      return {
        ...state,
        isRunning: !state.isRunning,
      };

In action, I have then tried the code like this, and export to use in my App.js, but also not working.

export const timer = () => (dispatch, getState) => {
  let timer = null;
  clearInterval(timer);
  if (getState().isRunning === true)
    timer = setInterval(() => {
      dispatch(incAction());
    }, 1000);
};

Please help, and let me know if any clarification needed. Thanks

like image 806
cHappiness Avatar asked Jan 21 '26 11:01

cHappiness


1 Answers

You should put side effects in the thunk, not in the reducer because the reducer has to be a synchronous pure function.

Here is an example of handling the side effects in the thunk:

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;

const initialState = {
  timer: { isRunning: false, time: 0 },
};
//selectors
const selectTimer = (state) => state.timer;
const selectIsRunning = createSelector(
  [selectTimer],
  (timer) => timer.isRunning
);
const selectTime = createSelector(
  [selectTimer],
  (timer) => timer.time
);
//action types
const TOGGLE = 'TOGGLE';
const UPDATE = 'UPDATE';
//action creators
const toggle = () => ({ type: TOGGLE });
const update = () => ({ type: UPDATE });

//thunks
const toggleTimer = () => (dispatch, getState) => {
  dispatch(toggle());
  const isRunning = selectIsRunning(getState());
  if (isRunning) {
    dispatch(recurUpdate());
  }
};
const recurUpdate = () => (dispatch, getState) =>
  setTimeout(() => {
    const isRunning = selectIsRunning(getState());
    if (isRunning) {
      dispatch(update());
      //recursively dispatch this thunk
      dispatch(recurUpdate());
    }
  }, 1000);
const reducer = (state, { type }) => {
  if (type === TOGGLE) {
    return {
      ...state,
      timer: {
        ...state.timer,
        isRunning: !state.timer.isRunning,
      },
    };
  }
  if (type === UPDATE) {
    return {
      ...state,
      timer: { ...state.timer, time: state.timer.time + 1 },
    };
  }
  return state;
};
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      ({ dispatch, getState }) =>
        (next) =>
        (
          action //simple thunk implementation
        ) =>
          typeof action === 'function'
            ? action(dispatch, getState)
            : next(action)
    )
  )
);
const App = () => {
  const dispatch = useDispatch();
  const isRunning = useSelector(selectIsRunning);
  const time = useSelector(selectTime);
  const timerHandler = React.useCallback(
    () => dispatch(toggleTimer()),
    [dispatch]
  );
  return (
    <div>
      <button onClick={timerHandler}>
        {isRunning ? 'TIMER STOP' : 'TIMER START'}
      </button>
      Time:{time}
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>

<div id="root"></div>

As a side note; it is better to store the time the timer has started and subtract that from the current time (Date.now()), you should use Math.ciel or Math.floor so you get precise seconds and run the thunk with requestAnimationFrame. You'll get a more precise value of seconds passed as what you get from relying on setTimeout but it does get complicated when you implement pause/continue as well (you have to add time paused to start time).

like image 102
HMR Avatar answered Jan 24 '26 02:01

HMR



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!