# Example: Reddit API This is the complete source code of the Reddit headline fetching example we built during the [advanced tutorial](README.md). ## Entry Point #### `index.js` ```js import 'babel-core/polyfill' import React from 'react' import { render } from 'react-dom' import Root from './containers/Root' render( , document.getElementById('root') ) ``` ## Action Creators and Constants #### `actions.js` ```js import fetch from 'isomorphic-fetch' export const REQUEST_POSTS = 'REQUEST_POSTS' export const RECEIVE_POSTS = 'RECEIVE_POSTS' export const SELECT_REDDIT = 'SELECT_REDDIT' export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT' export function selectReddit(reddit) { return { type: SELECT_REDDIT, reddit } } export function invalidateReddit(reddit) { return { type: INVALIDATE_REDDIT, reddit } } function requestPosts(reddit) { return { type: REQUEST_POSTS, reddit } } function receivePosts(reddit, json) { return { type: RECEIVE_POSTS, reddit, posts: json.data.children.map(child => child.data), receivedAt: Date.now() } } function fetchPosts(reddit) { return dispatch => { dispatch(requestPosts(reddit)) return fetch(`http://www.reddit.com/r/${reddit}.json`) .then(req => req.json()) .then(json => dispatch(receivePosts(reddit, json))) } } function shouldFetchPosts(state, reddit) { const posts = state.postsByReddit[reddit] if (!posts) { return true } else if (posts.isFetching) { return false } else { return posts.didInvalidate } } export function fetchPostsIfNeeded(reddit) { return (dispatch, getState) => { if (shouldFetchPosts(getState(), reddit)) { return dispatch(fetchPosts(reddit)) } } } ``` ## Reducers #### `reducers.js` ```js import { combineReducers } from 'redux' import { SELECT_REDDIT, INVALIDATE_REDDIT, REQUEST_POSTS, RECEIVE_POSTS } from './actions' function selectedReddit(state = 'reactjs', action) { switch (action.type) { case SELECT_REDDIT: return action.reddit default: return state } } function posts(state = { isFetching: false, didInvalidate: false, items: [] }, action) { switch (action.type) { case INVALIDATE_REDDIT: return Object.assign({}, state, { didInvalidate: true }) case REQUEST_POSTS: return Object.assign({}, state, { isFetching: true, didInvalidate: false }) case RECEIVE_POSTS: return Object.assign({}, state, { isFetching: false, didInvalidate: false, items: action.posts, lastUpdated: action.receivedAt }) default: return state } } function postsByReddit(state = { }, action) { switch (action.type) { case INVALIDATE_REDDIT: case RECEIVE_POSTS: case REQUEST_POSTS: return Object.assign({}, state, { [action.reddit]: posts(state[action.reddit], action) }) default: return state } } const rootReducer = combineReducers({ postsByReddit, selectedReddit }) export default rootReducer ``` ## Store #### `configureStore.js` ```js import { createStore, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' import rootReducer from './reducers' const loggerMiddleware = createLogger() const createStoreWithMiddleware = applyMiddleware( thunkMiddleware, loggerMiddleware )(createStore) export default function configureStore(initialState) { return createStoreWithMiddleware(rootReducer, initialState) } ``` ## Smart Components #### `containers/Root.js` ```js import React, { Component } from 'react' import { Provider } from 'react-redux' import configureStore from '../configureStore' import AsyncApp from './AsyncApp' const store = configureStore() export default class Root extends Component { render() { return ( ) } } ``` #### `containers/AsyncApp.js` ```js import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions' import Picker from '../components/Picker' import Posts from '../components/Posts' class AsyncApp 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(fetchPostsIfNeeded(selectedReddit)) } componentWillReceiveProps(nextProps) { if (nextProps.selectedReddit !== this.props.selectedReddit) { const { dispatch, selectedReddit } = nextProps dispatch(fetchPostsIfNeeded(selectedReddit)) } } handleChange(nextReddit) { this.props.dispatch(selectReddit(nextReddit)) } handleRefreshClick(e) { e.preventDefault() const { dispatch, selectedReddit } = this.props dispatch(invalidateReddit(selectedReddit)) dispatch(fetchPostsIfNeeded(selectedReddit)) } render() { const { selectedReddit, posts, isFetching, lastUpdated } = this.props return (

{lastUpdated && Last updated at {new Date(lastUpdated).toLocaleTimeString()}. {' '} } {!isFetching && Refresh }

{isFetching && posts.length === 0 &&

Loading...

} {!isFetching && posts.length === 0 &&

Empty.

} {posts.length > 0 &&
}
) } } AsyncApp.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)(AsyncApp) ``` ## Dumb Components #### `components/Picker.js` ```js import React, { Component, PropTypes } from 'react' export default class Picker extends Component { render() { const { value, onChange, options } = this.props return (

{value}

) } } Picker.propTypes = { options: PropTypes.arrayOf( PropTypes.string.isRequired ).isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired } ``` #### `components/Posts.js` ```js import React, { PropTypes, Component } from 'react' export default class Posts extends Component { render() { return ( ) } } Posts.propTypes = { posts: PropTypes.array.isRequired } ```