Skip to content

Commit

Permalink
added saga-async
Browse files Browse the repository at this point in the history
  • Loading branch information
vitsy committed Feb 12, 2018
1 parent b3b820a commit 57e66b2
Show file tree
Hide file tree
Showing 16 changed files with 906 additions and 4 deletions.
8 changes: 8 additions & 0 deletions examples/saga-async/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"presets": ["es2015", "react", "stage-0"],
"env": {
"development": {
"presets": ["react-hmre"]
}
}
}
52 changes: 52 additions & 0 deletions examples/saga-async/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

export function selectReddit(reddit) {
return {
type: 'selectedReddit.selectReddit',
reddit
}
}

export function invalidateReddit(reddit) {
return {
type: 'postsByReddit.invalidateReddit',
reddit
}
}

export function requestPosts(reddit) {
return {
type: 'postsByReddit.requestPosts',
reddit
}
}

export function receivePosts(reddit, posts) {
return {
type: 'postsByReddit.receivePosts',
reddit: reddit,
posts,
receivedAt: Date.now()
}
}

function fetchPosts(reddit) {
return {
type: '*fetchPosts',
reddit
}
}

function shouldFetchPosts(state, reddit) {
const posts = state.postsByReddit[reddit]
if (!posts) {
return true
}
if (posts.isFetching) {
return false
}
return posts.didInvalidate
}

export function startFetchPosts(reddit) {
return fetchPosts(reddit)
}
29 changes: 29 additions & 0 deletions examples/saga-async/components/Picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { Component, PropTypes } from 'react'

export default class Picker extends Component {
render() {
const { value, onChange, options } = this.props

return (
<span>
<h1>{value}</h1>
<select onChange={e => onChange(e.target.value)}
value={value}>
{options.map(option =>
<option value={option} key={option}>
{option}
</option>)
}
</select>
</span>
)
}
}

Picker.propTypes = {
options: PropTypes.arrayOf(
PropTypes.string.isRequired
).isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
}
17 changes: 17 additions & 0 deletions examples/saga-async/components/Posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { PropTypes, Component } from 'react'

export default class Posts extends Component {
render() {
return (
<ul>
{this.props.posts.map((post, i) =>
<li key={i}>{post.title}</li>
)}
</ul>
)
}
}

Posts.propTypes = {
posts: PropTypes.array.isRequired
}
98 changes: 98 additions & 0 deletions examples/saga-async/containers/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { selectReddit, startFetchPosts, invalidateReddit } from '../actions'
import Picker from '../components/Picker'
import Posts from '../components/Posts'

class App extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleRefreshClick = this.handleRefreshClick.bind(this)
}

componentDidMount() {
const { dispatch, selectedReddit } = this.props
dispatch(startFetchPosts(selectedReddit))
}

componentWillReceiveProps(nextProps) {
if (nextProps.selectedReddit !== this.props.selectedReddit) {
const { dispatch, selectedReddit } = nextProps
dispatch(startFetchPosts(selectedReddit))
}
}

handleChange(nextReddit) {
this.props.dispatch(selectReddit(nextReddit))
}

handleRefreshClick(e) {
e.preventDefault()

const { dispatch, selectedReddit } = this.props
dispatch(invalidateReddit(selectedReddit))
dispatch(startFetchPosts(selectedReddit))
}

render() {
const { selectedReddit, posts, isFetching, lastUpdated } = this.props
const isEmpty = posts.length === 0
return (
<div>
<Picker value={selectedReddit}
onChange={this.handleChange}
options={[ 'reactjs', 'frontend' ]} />
<p>
{lastUpdated &&
<span>
Last updated at {new Date(lastUpdated).toLocaleTimeString()}.
{' '}
</span>
}
{!isFetching &&
<a href="#"
onClick={this.handleRefreshClick}>
Refresh
</a>
}
</p>
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
: <div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Posts posts={posts} />
</div>
}
</div>
)
}
}

App.propTypes = {
selectedReddit: PropTypes.string.isRequired,
posts: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
lastUpdated: PropTypes.number,
dispatch: PropTypes.func.isRequired
}

function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}

return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}

export default connect(mapStateToProps)(App)
11 changes: 11 additions & 0 deletions examples/saga-async/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Redux async example</title>
</head>
<body>
<div id="root">
</div>
<script src="/static/bundle.js"></script>
</body>
</html>
17 changes: 17 additions & 0 deletions examples/saga-async/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import rootSaga from './sagas'
import App from './containers/App'
import configureStore from './store/configureStore'

const store = configureStore()
store.runSaga(rootSaga)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
54 changes: 54 additions & 0 deletions examples/saga-async/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "redux-async-example",
"version": "0.0.0",
"description": "Redux async example",
"scripts": {
"start": "node server.js"
},
"repository": {
"type": "git",
"url": "https://github.com/reactjs/redux.git"
},
"keywords": [
"react",
"reactjs",
"hot",
"reload",
"hmr",
"live",
"edit",
"webpack",
"flux"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/reactjs/redux/issues"
},
"homepage": "http://redux.js.org",
"dependencies": {
"babel-polyfill": "^6.3.14",
"isomorphic-fetch": "^2.1.1",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-redux": "^4.2.1",
"redux": "^3.2.1",
"redux-logger": "^2.4.0",
"redux-reducer-functions": "file:../../",
"redux-saga": "^0.16.0",
"redux-thunk": "^1.0.3"
},
"devDependencies": {
"babel-core": "^6.3.15",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-react-hmre": "^1.0.1",
"babel-preset-stage-0": "^6.24.1",
"expect": "^1.6.0",
"express": "^4.13.3",
"node-libs-browser": "^0.5.2",
"webpack": "^1.9.11",
"webpack-dev-middleware": "^1.2.0",
"webpack-hot-middleware": "^2.2.0"
}
}
11 changes: 11 additions & 0 deletions examples/saga-async/reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { combineReducers } from 'redux-reducer-functions'
import {selectReddit} from './reducerFuncs'
import * as postsByReddit from './reducerFuncs'


const rootReducer = combineReducers({
selectedReddit:{ selectReddit, other: (state = 'reactjs', action) => state},
postsByReddit
})

export default rootReducer
48 changes: 48 additions & 0 deletions examples/saga-async/reducers/reducerFuncs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@


const defStatePostsByReddit = {
isFetching: false,
didInvalidate: false,
items: []
}

export function selectReddit(state = 'reactjs', action) {
return action.reddit
}

function applyAction(state, action, additional){
const reddit = state[action.reddit] || defStatePostsByReddit
return{
...state,
[action.reddit]: { ...reddit, ...additional }
}
}

export function invalidateReddit(state, action) {
return applyAction (state, action, { didInvalidate: true } )
}

export function requestPosts(state, action) {

return applyAction (state, action, {
isFetching: true,
didInvalidate: false
}
)
}

export function receivePosts(state, action) {
return applyAction(state, action, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
}
)
}


export function other(state = {}, action) {
return state;
}

Loading

0 comments on commit 57e66b2

Please sign in to comment.