Skip to content

Instantly share code, notes, and snippets.

@pe3
Last active January 27, 2016 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pe3/8794b27999332231ccfd to your computer and use it in GitHub Desktop.
Save pe3/8794b27999332231ccfd to your computer and use it in GitHub Desktop.
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider, connect } from 'react-redux'
import sagaMiddleware, { take, put, fork } from 'redux-saga'
// React component
export class Counter extends React.Component {
render(){
const { value, clickPlus, clickMinus, clickSlow } = this.props
return (
<div className={this.props.className}>
<span>{value}</span>
<button onClick={clickMinus}>-</button>
<button onClick={clickPlus}>+</button>
<button onClick={clickSlow}>slow</button>
</div>
)
}
}
Counter.propTypes = {
value: PropTypes.number.isRequired,
clickPlus: PropTypes.func.isRequired,
clickMinus: PropTypes.func.isRequired,
clickSlow: PropTypes.func.isRequired
}
// Action:
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'
export const SET_COUNTER = 'SET_COUNTER'
export const INCREMENT_ASYNC = 'INCREMENT_ASYNC'
const increment = () => ({type: INCREMENT_COUNTER})
const decrement = () => ({type: DECREMENT_COUNTER})
const setCounter = (value) => ({type: SET_COUNTER, value: parseInt(value)})
const incrementAsync = () => ({type: INCREMENT_ASYNC})
// Reducer:
export function counter(state = 0, action) {
switch (action.type) {
case INCREMENT_COUNTER:
return state + 1
case DECREMENT_COUNTER:
return state - 1
case SET_COUNTER:
return action.value
default:
return state
}
}
// Sagas (like threads):
function* watchDelayedIncrement() {
while(true) {
const nextAction = yield take(INCREMENT_ASYNC)
yield delay(1000)
// after the delay, dispatch plain INCREMENT_COUNTER
yield put( increment() )
}
}
const delay = ms => (
new Promise(resolve => {
setTimeout(() => resolve(), ms)
})
)
function* updateWindowLocation(getState) {
while(true) {
const nextAction = yield take([INCREMENT_COUNTER, DECREMENT_COUNTER, SET_COUNTER])
window.location = "#" + getState().counter
put(nextAction)
}
}
function* rootSaga(getState) {
yield fork(watchDelayedIncrement)
yield fork(updateWindowLocation, getState)
}
// Store:
const createStoreWithSaga = applyMiddleware(
sagaMiddleware(rootSaga)
)(createStore)
const rootReducer = combineReducers({counter})
let store = createStoreWithSaga(rootReducer)
// Connected Component:
function mapStateToProps(state) {
return {
value: state.counter
}
}
function mapDispatchToProps(dispatch) {
return {
clickPlus: () => dispatch(increment()),
clickMinus: () => dispatch(decrement()),
clickSlow: () => dispatch(incrementAsync())
}
}
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
// initialize state once
const maybeId = window.location.hash.substr(1)
if(maybeId){
store.dispatch(setCounter(maybeId))
}
window.addEventListener("hashchange", (e) => {
const maybeId = window.location.hash.substr(1)
if(store.getState().counter !== maybeId) {
store.dispatch(setCounter(maybeId))
}
})
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('react-root')
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment