Last active
October 5, 2016 07:13
-
-
Save yelouafi/a245691d02c39cb244487bf6f42b5097 to your computer and use it in GitHub Desktop.
redux-saga with vanilla React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { runSaga, delay } from 'redux-saga' | |
import { take, put, call } from 'redux-saga/effects' | |
// helper functions | |
const bindFunc = (comp, method) => (...args) => method.apply(comp, args) | |
const bindSaga = (comp, method) => (...args) => runSaga(method.apply(comp, args), {}) | |
export default class Counter extends React.Component { | |
constructor() { | |
super() | |
this.state = {count: 0} | |
this.increment = bindFunc(this, this.increment) | |
this.decrement = bindFunc(this, this.decrement) | |
this.incrementAsync = bindSaga(this, this.incrementAsync) | |
} | |
increment() { | |
this.setState({count: this.state.count + 1}) | |
} | |
decrement() { | |
this.setState({count: this.state.count - 1}) | |
} | |
*incrementAsync() { | |
yield call(delay, 1000) | |
yield call(this.increment) | |
} | |
render() { | |
return ( | |
<p> | |
Clicked: {this.state.count} times | |
{' '} | |
<button onClick={this.increment}> | |
+ | |
</button> | |
{' '} | |
<button onClick={this.decrement}> | |
- | |
</button> | |
{' '} | |
<button onClick={this.incrementAsync}> | |
Increment async | |
</button> | |
</p> | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { runSaga, channel, takeLatest, delay } from 'redux-saga' | |
import { put, call, race } from 'redux-saga/effects' | |
import api from '..' | |
// You'll likely want to split this into Container/Presentation components | |
export default class ProductList extends React.Component { | |
constructor() { | |
super() | |
this.state = { | |
products: [], | |
isFetching: false, | |
fetchError: '' | |
} | |
// W'll use this channel to dispatch messages to our Saga from event callbacks | |
this.channel = channel() | |
} | |
// run the root saga | |
componentWillMount() { | |
this._task_ = runSaga(this.watchFetchProducts(), { | |
dispatch: data => this.setState(data) // yield put({...}) => this.setState({...}) | |
}) | |
} | |
componentWillUnmount() { | |
// cancel any pending fetch | |
this._task_.cancel() | |
} | |
*fetchProducts(timeout) { | |
yield put({isFetching: true, fetchError: ''}) // this will resolve to setState({isFetching: true, ...}) | |
try { | |
const winner = yield race({ | |
response: call(api.getProducts), | |
timeout: call(delay, timeout) | |
}) | |
if(winner.response) { | |
yield put({products: winner.response, isFetching: false}) | |
} else { | |
throw 'Request timeout!' | |
} | |
} catch(error) { | |
yield put({isFetching: false, fetchError: error}) | |
} | |
} | |
*watchFetchProducts() { | |
yield* takeLatest(this.channel, this.fetchProducts.bind(this)) | |
} | |
render() { | |
return ( | |
<div> | |
<button onClick={() => this.channel.put(1000 /*timeout*/)}>Fetch products</button> | |
{ this.state.isFetching ? | |
<p>Fetching products ...</p> | |
: this.state.fetchError ? | |
<p>{this.state.fetchError}</p> | |
: /* has users */ | |
<ul>{ | |
this.state.products.map(product => | |
<li key={product.id}> | |
{product.title} | |
</li> | |
) | |
}</ul> | |
} | |
</div> | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment