-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Creating a stopwatch without side effects in the reducer? #1194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Calling clearInterval inside the reducer is certainly a bad practice, as it makes the reducer non-pure. A common way and straightforward way here would be to clear the interval inside your stop() function before you call dispatch. I've forked your fiddle and implemented what I'd do: https://jsfiddle.net/uu71ka1e/1/. Basically, I add another subscriber to the react store, and have it cancel or start the time depending on whether the isOn property is true in your state. |
The answer by @winstonewert is correct. const { createStore } = Redux;
// Initial state for reducer
const initialState = {
isOn: false,
time: 0
};
// Reducer function
const timer = (state = initialState, action) => {
switch (action.type) {
case 'START_TIMER':
return {
...initialState,
isOn: true,
offset: action.offset,
};
case 'STOP_TIMER':
return {
isOn: false,
time: state.time
};
case 'TICK':
return {
...state,
time: state.time + (action.time - state.offset),
offset: action.time
};
default:
return state;
}
}
// Create store using the reducer
const store = createStore(timer);
// React Component to display the timer
class Timer extends React.Component {
constructor() {
super();
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
start() {
store.dispatch({
type: 'START_TIMER',
offset: Date.now(),
});
}
stop() {
store.dispatch({
type: 'STOP_TIMER'
});
}
format(time) {
const pad = (time, length) => {
while (time.length < length) {
time = '0' + time;
}
return time;
}
time = new Date(time);
let m = pad(time.getMinutes().toString(), 2);
let s = pad(time.getSeconds().toString(), 2);
let ms = pad(time.getMilliseconds().toString(), 3);
return `${m} : ${s} . ${ms}`;
}
render() {
return (
<div>
<h1>Time: {this.format(this.props.time)}</h1>
<button onClick={this.props.isOn ? this.stop : this.start}>
{ this.props.isOn ? 'Stop' : 'Start' }
</button>
</div>
);
}
}
// render function that runs everytime an action is dispatched
const render = () => {
ReactDOM.render(
<Timer
time={store.getState().time}
isOn={store.getState().isOn}
interval={store.getState().interval}
/>,
document.getElementById('app')
);
}
store.subscribe(render);
var interval = null;
store.subscribe(() => {
if (store.getState().isOn && interval === null) {
interval = setInterval(() => {
store.dispatch({
type: 'TICK',
time: Date.now()
});
});
}
if (!store.getState().isOn && interval !== null) {
clearInterval(interval);
interval = null;
}
});
render(); |
@winstonewert your example was super helpful to me today, thank you! |
Hey guy I really appreciate this example! I am getting a syntax error at ...initialState and at ...state case 'START_TIMER': case 'TICK': Thanks for your help! |
@aph-vsn, that is new syntax that may not be supported by your development environment. Are you using babel or another transpiler? |
@aph-vsn : Yeah, the Object Spread operator is a not-yet-final piece of syntax. See http://redux.js.org/docs/recipes/UsingObjectSpreadOperator.html for more information on using it. Also, note that the Create-React-App tool includes the plugin needed to use the Object Spread operator, so that works out of the box in projects started using Create-React-App. |
@winstonewert |
Just use |
Ok I fixed the ...initialState. I got a different problem now. I am using the same code here. Uncaught TypeError: Cannot read property 'subscribe' of undefined(…) |
@aph-vsn : A couple suggestions: First, you should use triple backticks to properly format code blocks on Github, like: ```js Second, the discussion is probably best moved to Stack Overflow at this point - you'll get better help there trying to work out code issues. |
ok, if you have an idea, please let me know! Thank you |
Third, what do you use for bundling? Is there no one error in your terminal? You have named export |
No error in my terminal. |
if anybody would like to help me : here is the stackoverflow https://stackoverflow.com/questions/40873488/reactjs-uncaught-typeerror-cannot-read-property-subscribe-of-undefined |
I got it. Thank you @TrySound you were right |
clicking "Stop" then "Start" again making it start again from 00:00.000, I think the right script for 'START_TIMER' is |
I've been trying to create a stopwatch with React/Redux. I have a working example in a JSFiddle here. The approach I took was to have a
START_TIMER
action which in turn usessetInterval
to fire offTICK
actions which calculate how much time has passed and add that to the current time.I'm not sure if this approach is valid because I do a
clearInterval
in theSTOP_TIMER
part of my reducer, which I assume is a side effect, and my reducer is no longer a pure function. I am pretty new to redux, so I may just be thinking about doing this the wrong way. Is my current example a bad practice in redux, and is there a better way to achieve what I am trying to do? Thanks a lot.The text was updated successfully, but these errors were encountered: