diff --git a/.eslintrc b/.eslintrc index 99c5dbda67..e4c371f5ce 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,12 +1,12 @@ { - "extends": "rackt", - "rules": { - "valid-jsdoc": 2, - // Disable until Flow supports let and const - "no-var": 0, - "react/jsx-uses-react": 1, - "react/jsx-no-undef": 2, - "react/wrap-multilines": 2 + "extends": "airbnb", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "experimentalObjectRestSpread": true + }, }, "plugins": [ "react" diff --git a/build/es3ify.js b/build/es3ify.js index f4639cf400..e7b501c163 100644 --- a/build/es3ify.js +++ b/build/es3ify.js @@ -1,25 +1,25 @@ -var glob = require('glob') -var fs = require('fs') -var es3ify = require('es3ify') +const glob = require('glob'); +const fs = require('fs'); +const es3ify = require('es3ify'); -glob('./@(lib|dist|es)/**/*.js', function (err, files) { +glob('./@(lib|dist|es)/**/*.js', (err, files) => { if (err) { - throw err + throw err; } - files.forEach(function (file) { - fs.readFile(file, 'utf8', function (err, data) { - if (err) { - throw err + files.forEach((file) => { + fs.readFile(file, 'utf8', (error, data) => { + if (error) { + throw error; } - fs.writeFile(file, es3ify.transform(data), function (err) { - if (err) { - throw err + fs.writeFile(file, es3ify.transform(data), (writeErr) => { + if (writeErr) { + throw writeErr; } - console.log('es3ified ' + file) // eslint-disable-line no-console - }) - }) - }) -}) + console.log(`es3ified ${file}`); // eslint-disable-line no-console + }); + }); + }); +}); diff --git a/build/use-lodash-es.js b/build/use-lodash-es.js index ba4f2f52e6..2f3876ce47 100644 --- a/build/use-lodash-es.js +++ b/build/use-lodash-es.js @@ -1,10 +1,10 @@ -module.exports = function () { +module.exports = function useLodashEs() { return { visitor: { ImportDeclaration(path) { - var source = path.node.source - source.value = source.value.replace(/^lodash($|\/)/, 'lodash-es$1') - } - } - } -} + const source = path.node.source; + source.value = source.value.replace(/^lodash($|\/)/, 'lodash-es$1'); + }, + }, + }; +}; diff --git a/examples/async/actions/index.js b/examples/async/actions/index.js index b3c32cd68d..c6cafd23eb 100644 --- a/examples/async/actions/index.js +++ b/examples/async/actions/index.js @@ -1,64 +1,65 @@ -import fetch from 'isomorphic-fetch' +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 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 - } + reddit, + }; } export function invalidateReddit(reddit) { return { type: INVALIDATE_REDDIT, - reddit - } + reddit, + }; } function requestPosts(reddit) { return { type: REQUEST_POSTS, - reddit - } + reddit, + }; } function receivePosts(reddit, json) { return { type: RECEIVE_POSTS, - reddit: reddit, + reddit, posts: json.data.children.map(child => child.data), - receivedAt: Date.now() - } + receivedAt: Date.now(), + }; } function fetchPosts(reddit) { return dispatch => { - dispatch(requestPosts(reddit)) + dispatch(requestPosts(reddit)); return fetch(`https://www.reddit.com/r/${reddit}.json`) .then(response => response.json()) - .then(json => dispatch(receivePosts(reddit, json))) - } + .then(json => dispatch(receivePosts(reddit, json))); + }; } function shouldFetchPosts(state, reddit) { - const posts = state.postsByReddit[reddit] + const posts = state.postsByReddit[reddit]; if (!posts) { - return true + return true; } if (posts.isFetching) { - return false + return false; } - return posts.didInvalidate + return posts.didInvalidate; } export function fetchPostsIfNeeded(reddit) { return (dispatch, getState) => { if (shouldFetchPosts(getState(), reddit)) { - return dispatch(fetchPosts(reddit)) + return dispatch(fetchPosts(reddit)); } - } + return null; + }; } diff --git a/examples/async/components/Picker.js b/examples/async/components/Picker.js index be78acb182..66b2292f7c 100644 --- a/examples/async/components/Picker.js +++ b/examples/async/components/Picker.js @@ -1,29 +1,27 @@ -import React, { Component, PropTypes } from 'react' +import React, { PropTypes } from 'react'; -export default class Picker extends Component { - render() { - const { value, onChange, options } = this.props - - return ( - -

{value}

- -
- ) - } -} +const Picker = ({ value, onChange, options }) => ( + +

{value}

+ +
+); Picker.propTypes = { options: PropTypes.arrayOf( PropTypes.string.isRequired ).isRequired, value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired -} + onChange: PropTypes.func.isRequired, +}; + +export default Picker; diff --git a/examples/async/components/Posts.js b/examples/async/components/Posts.js index dd3285dab9..d99ab58a37 100644 --- a/examples/async/components/Posts.js +++ b/examples/async/components/Posts.js @@ -1,17 +1,15 @@ -import React, { PropTypes, Component } from 'react' +import React, { PropTypes } from 'react'; -export default class Posts extends Component { - render() { - return ( - - ) - } -} +const Posts = ({ posts }) => ( + +); Posts.propTypes = { - posts: PropTypes.array.isRequired -} + posts: PropTypes.array.isRequired, +}; + +export default Posts; diff --git a/examples/async/containers/App.js b/examples/async/containers/App.js index 6109878fca..f4791c7162 100644 --- a/examples/async/containers/App.js +++ b/examples/async/containers/App.js @@ -1,48 +1,50 @@ -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' +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 App extends Component { constructor(props) { - super(props) - this.handleChange = this.handleChange.bind(this) - this.handleRefreshClick = this.handleRefreshClick.bind(this) + super(props); + this.handleChange = this.handleChange.bind(this); + this.handleRefreshClick = this.handleRefreshClick.bind(this); } componentDidMount() { - const { dispatch, selectedReddit } = this.props - dispatch(fetchPostsIfNeeded(selectedReddit)) + const { dispatch, selectedReddit } = this.props; + dispatch(fetchPostsIfNeeded(selectedReddit)); } componentWillReceiveProps(nextProps) { if (nextProps.selectedReddit !== this.props.selectedReddit) { - const { dispatch, selectedReddit } = nextProps - dispatch(fetchPostsIfNeeded(selectedReddit)) + const { dispatch, selectedReddit } = nextProps; + dispatch(fetchPostsIfNeeded(selectedReddit)); } } handleChange(nextReddit) { - this.props.dispatch(selectReddit(nextReddit)) + this.props.dispatch(selectReddit(nextReddit)); } handleRefreshClick(e) { - e.preventDefault() + e.preventDefault(); - const { dispatch, selectedReddit } = this.props - dispatch(invalidateReddit(selectedReddit)) - dispatch(fetchPostsIfNeeded(selectedReddit)) + const { dispatch, selectedReddit } = this.props; + dispatch(invalidateReddit(selectedReddit)); + dispatch(fetchPostsIfNeeded(selectedReddit)); } render() { - const { selectedReddit, posts, isFetching, lastUpdated } = this.props - const isEmpty = posts.length === 0 + const { selectedReddit, posts, isFetching, lastUpdated } = this.props; + const isEmpty = posts.length === 0; + const message = isFetching ?

Loading...

:

Empty.

; return (
+ onChange={this.handleChange} + options={['reactjs', 'frontend']} + />

{lastUpdated && @@ -52,19 +54,18 @@ class App extends Component { } {!isFetching && + onClick={this.handleRefreshClick} + > Refresh }

- {isEmpty - ? (isFetching ?

Loading...

:

Empty.

) - :
+ {isEmpty ? message :
- } + }
- ) + ); } } @@ -73,26 +74,26 @@ App.propTypes = { posts: PropTypes.array.isRequired, isFetching: PropTypes.bool.isRequired, lastUpdated: PropTypes.number, - dispatch: PropTypes.func.isRequired -} + dispatch: PropTypes.func.isRequired, +}; function mapStateToProps(state) { - const { selectedReddit, postsByReddit } = state + const { selectedReddit, postsByReddit } = state; const { isFetching, lastUpdated, - items: posts + items: posts, } = postsByReddit[selectedReddit] || { isFetching: true, - items: [] - } + items: [], + }; return { selectedReddit, posts, isFetching, - lastUpdated - } + lastUpdated, + }; } -export default connect(mapStateToProps)(App) +export default connect(mapStateToProps)(App); diff --git a/examples/async/index.js b/examples/async/index.js index 12bcb25c37..ab639cc882 100644 --- a/examples/async/index.js +++ b/examples/async/index.js @@ -1,15 +1,15 @@ -import 'babel-polyfill' -import React from 'react' -import { render } from 'react-dom' -import { Provider } from 'react-redux' -import App from './containers/App' -import configureStore from './store/configureStore' +import 'babel-polyfill'; +import React from 'react'; +import { render } from 'react-dom'; +import { Provider } from 'react-redux'; +import App from './containers/App'; +import configureStore from './store/configureStore'; -const store = configureStore() +const store = configureStore(); render( , document.getElementById('root') -) +); diff --git a/examples/async/reducers/index.js b/examples/async/reducers/index.js index f936cbced3..d79c407a33 100644 --- a/examples/async/reducers/index.js +++ b/examples/async/reducers/index.js @@ -1,42 +1,42 @@ -import { combineReducers } from 'redux' +import { combineReducers } from 'redux'; import { SELECT_REDDIT, INVALIDATE_REDDIT, - REQUEST_POSTS, RECEIVE_POSTS -} from '../actions' + REQUEST_POSTS, RECEIVE_POSTS, +} from '../actions'; function selectedReddit(state = 'reactjs', action) { switch (action.type) { case SELECT_REDDIT: - return action.reddit + return action.reddit; default: - return state + return state; } } function posts(state = { isFetching: false, didInvalidate: false, - items: [] + items: [], }, action) { switch (action.type) { case INVALIDATE_REDDIT: return Object.assign({}, state, { - didInvalidate: true - }) + didInvalidate: true, + }); case REQUEST_POSTS: return Object.assign({}, state, { isFetching: true, - didInvalidate: false - }) + didInvalidate: false, + }); case RECEIVE_POSTS: return Object.assign({}, state, { isFetching: false, didInvalidate: false, items: action.posts, - lastUpdated: action.receivedAt - }) + lastUpdated: action.receivedAt, + }); default: - return state + return state; } } @@ -46,16 +46,16 @@ function postsByReddit(state = { }, action) { case RECEIVE_POSTS: case REQUEST_POSTS: return Object.assign({}, state, { - [action.reddit]: posts(state[action.reddit], action) - }) + [action.reddit]: posts(state[action.reddit], action), + }); default: - return state + return state; } } const rootReducer = combineReducers({ postsByReddit, - selectedReddit -}) + selectedReddit, +}); -export default rootReducer +export default rootReducer; diff --git a/examples/async/store/configureStore.js b/examples/async/store/configureStore.js index 465d94919a..8767d763e7 100644 --- a/examples/async/store/configureStore.js +++ b/examples/async/store/configureStore.js @@ -1,22 +1,22 @@ -import { createStore, applyMiddleware } from 'redux' -import thunkMiddleware from 'redux-thunk' -import createLogger from 'redux-logger' -import rootReducer from '../reducers' +import { createStore, applyMiddleware } from 'redux'; +import thunkMiddleware from 'redux-thunk'; +import createLogger from 'redux-logger'; +import rootReducer from '../reducers'; export default function configureStore(initialState) { const store = createStore( rootReducer, initialState, applyMiddleware(thunkMiddleware, createLogger()) - ) + ); if (module.hot) { // Enable Webpack hot module replacement for reducers module.hot.accept('../reducers', () => { - const nextRootReducer = require('../reducers').default - store.replaceReducer(nextRootReducer) - }) + const nextRootReducer = require('../reducers').default; + store.replaceReducer(nextRootReducer); + }); } - return store + return store; } diff --git a/examples/buildAll.js b/examples/buildAll.js index 82388984b0..6cd8ed73cf 100644 --- a/examples/buildAll.js +++ b/examples/buildAll.js @@ -2,35 +2,35 @@ * Runs an ordered set of commands within each of the build directories. */ -import fs from 'fs' -import path from 'path' -import { spawnSync } from 'child_process' +import fs from 'fs'; +import path from 'path'; +import { spawnSync } from 'child_process'; -var exampleDirs = fs.readdirSync(__dirname).filter((file) => { - return fs.statSync(path.join(__dirname, file)).isDirectory() -}) +const exampleDirs = fs.readdirSync(__dirname).filter((file) => + fs.statSync(path.join(__dirname, file)).isDirectory() +); // Ordering is important here. `npm install` must come first. -var cmdArgs = [ - { cmd: 'npm', args: [ 'install' ] }, - { cmd: 'webpack', args: [ 'index.js' ] } -] +const cmdArgs = [ + { cmd: 'npm', args: ['install'] }, + { cmd: 'webpack', args: ['index.js'] }, +]; for (const dir of exampleDirs) { for (const cmdArg of cmdArgs) { // declare opts in this scope to avoid https://github.com/joyent/node/issues/9158 const opts = { cwd: path.join(__dirname, dir), - stdio: 'inherit' - } - let result = {} + stdio: 'inherit', + }; + let result = {}; if (process.platform === 'win32') { - result = spawnSync(cmdArg.cmd + '.cmd', cmdArg.args, opts) + result = spawnSync(`${cmdArg.cmd}.cmd`, cmdArg.args, opts); } else { - result = spawnSync(cmdArg.cmd, cmdArg.args, opts) + result = spawnSync(cmdArg.cmd, cmdArg.args, opts); } if (result.status !== 0) { - throw new Error('Building examples exited with non-zero') + throw new Error('Building examples exited with non-zero'); } } } diff --git a/examples/counter/components/Counter.js b/examples/counter/components/Counter.js index ef473bf933..0007289e2a 100644 --- a/examples/counter/components/Counter.js +++ b/examples/counter/components/Counter.js @@ -1,24 +1,24 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component, PropTypes } from 'react'; class Counter extends Component { constructor(props) { - super(props) - this.incrementAsync = this.incrementAsync.bind(this) - this.incrementIfOdd = this.incrementIfOdd.bind(this) + super(props); + this.incrementAsync = this.incrementAsync.bind(this); + this.incrementIfOdd = this.incrementIfOdd.bind(this); } incrementIfOdd() { if (this.props.value % 2 !== 0) { - this.props.onIncrement() + this.props.onIncrement(); } } incrementAsync() { - setTimeout(this.props.onIncrement, 1000) + setTimeout(this.props.onIncrement, 1000); } render() { - const { value, onIncrement, onDecrement } = this.props + const { value, onIncrement, onDecrement } = this.props; return (

Clicked: {value} times @@ -39,14 +39,14 @@ class Counter extends Component { Increment async

- ) + ); } } Counter.propTypes = { value: PropTypes.number.isRequired, onIncrement: PropTypes.func.isRequired, - onDecrement: PropTypes.func.isRequired -} + onDecrement: PropTypes.func.isRequired, +}; -export default Counter +export default Counter; diff --git a/examples/counter/index.js b/examples/counter/index.js index 60cac9cf27..8b18cc420f 100644 --- a/examples/counter/index.js +++ b/examples/counter/index.js @@ -1,11 +1,11 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import { createStore } from 'redux' -import Counter from './components/Counter' -import counter from './reducers' +import React from 'react'; +import ReactDOM from 'react-dom'; +import { createStore } from 'redux'; +import Counter from './components/Counter'; +import counter from './reducers'; -const store = createStore(counter) -const rootEl = document.getElementById('root') +const store = createStore(counter); +const rootEl = document.getElementById('root'); function render() { ReactDOM.render( @@ -14,9 +14,9 @@ function render() { onIncrement={() => store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, - rootEl - ) + rootEl + ); } -render() -store.subscribe(render) +render(); +store.subscribe(render); diff --git a/examples/counter/reducers/index.js b/examples/counter/reducers/index.js index 49590e9b05..b59edeb76a 100644 --- a/examples/counter/reducers/index.js +++ b/examples/counter/reducers/index.js @@ -1,10 +1,10 @@ export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': - return state + 1 + return state + 1; case 'DECREMENT': - return state - 1 + return state - 1; default: - return state + return state; } } diff --git a/examples/counter/test/components/Counter.spec.js b/examples/counter/test/components/Counter.spec.js index 4e5258d0ad..874d835136 100644 --- a/examples/counter/test/components/Counter.spec.js +++ b/examples/counter/test/components/Counter.spec.js @@ -1,67 +1,67 @@ -import expect from 'expect' -import React from 'react' -import { shallow } from 'enzyme' -import Counter from '../../components/Counter' +import expect from 'expect'; +import React from 'react'; +import { shallow } from 'enzyme'; +import Counter from '../../components/Counter'; function setup(value = 0) { const actions = { onIncrement: expect.createSpy(), - onDecrement: expect.createSpy() - } + onDecrement: expect.createSpy(), + }; const component = shallow( - ) + ); return { - component: component, - actions: actions, + component, + actions, buttons: component.find('button'), - p: component.find('p') - } + p: component.find('p'), + }; } describe('Counter component', () => { it('should display count', () => { - const { p } = setup() - expect(p.text()).toMatch(/^Clicked: 0 times/) - }) + const { p } = setup(); + expect(p.text()).toMatch(/^Clicked: 0 times/); + }); it('first button should call onIncrement', () => { - const { buttons, actions } = setup() - buttons.at(0).simulate('click') - expect(actions.onIncrement).toHaveBeenCalled() - }) + const { buttons, actions } = setup(); + buttons.at(0).simulate('click'); + expect(actions.onIncrement).toHaveBeenCalled(); + }); it('second button should call onDecrement', () => { - const { buttons, actions } = setup() - buttons.at(1).simulate('click') - expect(actions.onDecrement).toHaveBeenCalled() - }) + const { buttons, actions } = setup(); + buttons.at(1).simulate('click'); + expect(actions.onDecrement).toHaveBeenCalled(); + }); it('third button should not call onIncrement if the counter is even', () => { - const { buttons, actions } = setup(42) - buttons.at(2).simulate('click') - expect(actions.onIncrement).toNotHaveBeenCalled() - }) + const { buttons, actions } = setup(42); + buttons.at(2).simulate('click'); + expect(actions.onIncrement).toNotHaveBeenCalled(); + }); it('third button should call onIncrement if the counter is odd', () => { - const { buttons, actions } = setup(43) - buttons.at(2).simulate('click') - expect(actions.onIncrement).toHaveBeenCalled() - }) + const { buttons, actions } = setup(43); + buttons.at(2).simulate('click'); + expect(actions.onIncrement).toHaveBeenCalled(); + }); it('third button should call onIncrement if the counter is odd and negative', () => { - const { buttons, actions } = setup(-43) - buttons.at(2).simulate('click') - expect(actions.onIncrement).toHaveBeenCalled() - }) + const { buttons, actions } = setup(-43); + buttons.at(2).simulate('click'); + expect(actions.onIncrement).toHaveBeenCalled(); + }); it('fourth button should call onIncrement in a second', (done) => { - const { buttons, actions } = setup() - buttons.at(3).simulate('click') + const { buttons, actions } = setup(); + buttons.at(3).simulate('click'); setTimeout(() => { - expect(actions.onIncrement).toHaveBeenCalled() - done() - }, 1000) - }) -}) + expect(actions.onIncrement).toHaveBeenCalled(); + done(); + }, 1000); + }); +}); diff --git a/examples/counter/test/reducers/counter.spec.js b/examples/counter/test/reducers/counter.spec.js index c51d16adf4..411990c547 100644 --- a/examples/counter/test/reducers/counter.spec.js +++ b/examples/counter/test/reducers/counter.spec.js @@ -1,22 +1,22 @@ -import expect from 'expect' -import counter from '../../reducers' +import expect from 'expect'; +import counter from '../../reducers'; describe('reducers', () => { describe('counter', () => { it('should provide the initial state', () => { - expect(counter(undefined, {})).toBe(0) - }) + expect(counter(undefined, {})).toBe(0); + }); it('should handle INCREMENT action', () => { - expect(counter(1, { type: 'INCREMENT' })).toBe(2) - }) + expect(counter(1, { type: 'INCREMENT' })).toBe(2); + }); it('should handle DECREMENT action', () => { - expect(counter(1, { type: 'DECREMENT' })).toBe(0) - }) + expect(counter(1, { type: 'DECREMENT' })).toBe(0); + }); it('should ignore unknown actions', () => { - expect(counter(1, { type: 'unknown' })).toBe(1) - }) - }) -}) + expect(counter(1, { type: 'unknown' })).toBe(1); + }); + }); +}); diff --git a/examples/real-world/actions/index.js b/examples/real-world/actions/index.js index 2692fcb069..78961be66e 100644 --- a/examples/real-world/actions/index.js +++ b/examples/real-world/actions/index.js @@ -1,66 +1,66 @@ -import { CALL_API, Schemas } from '../middleware/api' +import { CALL_API, Schemas } from '../middleware/api'; -export const USER_REQUEST = 'USER_REQUEST' -export const USER_SUCCESS = 'USER_SUCCESS' -export const USER_FAILURE = 'USER_FAILURE' +export const USER_REQUEST = 'USER_REQUEST'; +export const USER_SUCCESS = 'USER_SUCCESS'; +export const USER_FAILURE = 'USER_FAILURE'; // Fetches a single user from Github API. // Relies on the custom API middleware defined in ../middleware/api.js. function fetchUser(login) { return { [CALL_API]: { - types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ], + types: [USER_REQUEST, USER_SUCCESS, USER_FAILURE], endpoint: `users/${login}`, - schema: Schemas.USER - } - } + schema: Schemas.USER, + }, + }; } // Fetches a single user from Github API unless it is cached. // Relies on Redux Thunk middleware. export function loadUser(login, requiredFields = []) { return (dispatch, getState) => { - const user = getState().entities.users[login] + const user = getState().entities.users[login]; if (user && requiredFields.every(key => user.hasOwnProperty(key))) { - return null + return null; } - return dispatch(fetchUser(login)) - } + return dispatch(fetchUser(login)); + }; } -export const REPO_REQUEST = 'REPO_REQUEST' -export const REPO_SUCCESS = 'REPO_SUCCESS' -export const REPO_FAILURE = 'REPO_FAILURE' +export const REPO_REQUEST = 'REPO_REQUEST'; +export const REPO_SUCCESS = 'REPO_SUCCESS'; +export const REPO_FAILURE = 'REPO_FAILURE'; // Fetches a single repository from Github API. // Relies on the custom API middleware defined in ../middleware/api.js. function fetchRepo(fullName) { return { [CALL_API]: { - types: [ REPO_REQUEST, REPO_SUCCESS, REPO_FAILURE ], + types: [REPO_REQUEST, REPO_SUCCESS, REPO_FAILURE], endpoint: `repos/${fullName}`, - schema: Schemas.REPO - } - } + schema: Schemas.REPO, + }, + }; } // Fetches a single repository from Github API unless it is cached. // Relies on Redux Thunk middleware. export function loadRepo(fullName, requiredFields = []) { return (dispatch, getState) => { - const repo = getState().entities.repos[fullName] + const repo = getState().entities.repos[fullName]; if (repo && requiredFields.every(key => repo.hasOwnProperty(key))) { - return null + return null; } - return dispatch(fetchRepo(fullName)) - } + return dispatch(fetchRepo(fullName)); + }; } -export const STARRED_REQUEST = 'STARRED_REQUEST' -export const STARRED_SUCCESS = 'STARRED_SUCCESS' -export const STARRED_FAILURE = 'STARRED_FAILURE' +export const STARRED_REQUEST = 'STARRED_REQUEST'; +export const STARRED_SUCCESS = 'STARRED_SUCCESS'; +export const STARRED_FAILURE = 'STARRED_FAILURE'; // Fetches a page of starred repos by a particular user. // Relies on the custom API middleware defined in ../middleware/api.js. @@ -68,11 +68,11 @@ function fetchStarred(login, nextPageUrl) { return { login, [CALL_API]: { - types: [ STARRED_REQUEST, STARRED_SUCCESS, STARRED_FAILURE ], + types: [STARRED_REQUEST, STARRED_SUCCESS, STARRED_FAILURE], endpoint: nextPageUrl, - schema: Schemas.REPO_ARRAY - } - } + schema: Schemas.REPO_ARRAY, + }, + }; } // Fetches a page of starred repos by a particular user. @@ -82,20 +82,20 @@ export function loadStarred(login, nextPage) { return (dispatch, getState) => { const { nextPageUrl = `users/${login}/starred`, - pageCount = 0 - } = getState().pagination.starredByUser[login] || {} + pageCount = 0, + } = getState().pagination.starredByUser[login] || {}; if (pageCount > 0 && !nextPage) { - return null + return null; } - return dispatch(fetchStarred(login, nextPageUrl)) - } + return dispatch(fetchStarred(login, nextPageUrl)); + }; } -export const STARGAZERS_REQUEST = 'STARGAZERS_REQUEST' -export const STARGAZERS_SUCCESS = 'STARGAZERS_SUCCESS' -export const STARGAZERS_FAILURE = 'STARGAZERS_FAILURE' +export const STARGAZERS_REQUEST = 'STARGAZERS_REQUEST'; +export const STARGAZERS_SUCCESS = 'STARGAZERS_SUCCESS'; +export const STARGAZERS_FAILURE = 'STARGAZERS_FAILURE'; // Fetches a page of stargazers for a particular repo. // Relies on the custom API middleware defined in ../middleware/api.js. @@ -103,11 +103,11 @@ function fetchStargazers(fullName, nextPageUrl) { return { fullName, [CALL_API]: { - types: [ STARGAZERS_REQUEST, STARGAZERS_SUCCESS, STARGAZERS_FAILURE ], + types: [STARGAZERS_REQUEST, STARGAZERS_SUCCESS, STARGAZERS_FAILURE], endpoint: nextPageUrl, - schema: Schemas.USER_ARRAY - } - } + schema: Schemas.USER_ARRAY, + }, + }; } // Fetches a page of stargazers for a particular repo. @@ -117,22 +117,22 @@ export function loadStargazers(fullName, nextPage) { return (dispatch, getState) => { const { nextPageUrl = `repos/${fullName}/stargazers`, - pageCount = 0 - } = getState().pagination.stargazersByRepo[fullName] || {} + pageCount = 0, + } = getState().pagination.stargazersByRepo[fullName] || {}; if (pageCount > 0 && !nextPage) { - return null + return null; } - return dispatch(fetchStargazers(fullName, nextPageUrl)) - } + return dispatch(fetchStargazers(fullName, nextPageUrl)); + }; } -export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE' +export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE'; // Resets the currently visible error message. export function resetErrorMessage() { return { - type: RESET_ERROR_MESSAGE - } + type: RESET_ERROR_MESSAGE, + }; } diff --git a/examples/real-world/components/Explore.js b/examples/real-world/components/Explore.js index e83051d3bb..30bc6e310f 100644 --- a/examples/real-world/components/Explore.js +++ b/examples/real-world/components/Explore.js @@ -1,39 +1,39 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component, PropTypes } from 'react'; -const GITHUB_REPO = 'https://github.com/reactjs/redux' +const GITHUB_REPO = 'https://github.com/reactjs/redux'; export default class Explore extends Component { constructor(props) { - super(props) - this.handleKeyUp = this.handleKeyUp.bind(this) - this.handleGoClick = this.handleGoClick.bind(this) + super(props); + this.handleKeyUp = this.handleKeyUp.bind(this); + this.handleGoClick = this.handleGoClick.bind(this); } componentWillReceiveProps(nextProps) { if (nextProps.value !== this.props.value) { - this.setInputValue(nextProps.value) + this.setInputValue(nextProps.value); } } getInputValue() { - return this.refs.input.value + return this.refs.input.value; } setInputValue(val) { // Generally mutating DOM is a bad idea in React components, // but doing this for a single uncontrolled field is less fuss // than making it controlled and maintaining a state for it. - this.refs.input.value = val + this.refs.input.value = val; } handleKeyUp(e) { if (e.keyCode === 13) { - this.handleGoClick() + this.handleGoClick(); } } handleGoClick() { - this.props.onChange(this.getInputValue()) + this.props.onChange(this.getInputValue()); } render() { @@ -41,9 +41,10 @@ export default class Explore extends Component {

Type a username or repo full name and hit 'Go':

+ ref="input" + defaultValue={this.props.value} + onKeyUp={this.handleKeyUp} + /> @@ -54,11 +55,11 @@ export default class Explore extends Component { Move the DevTools with Ctrl+W or hide them with Ctrl+H.

- ) + ); } } Explore.propTypes = { value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired -} + onChange: PropTypes.func.isRequired, +}; diff --git a/examples/real-world/components/List.js b/examples/real-world/components/List.js index 9fad1c9ddd..0fdb7df2e8 100644 --- a/examples/real-world/components/List.js +++ b/examples/real-world/components/List.js @@ -1,31 +1,29 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component, PropTypes } from 'react'; export default class List extends Component { renderLoadMore() { - const { isFetching, onLoadMoreClick } = this.props + const { isFetching, onLoadMoreClick } = this.props; return ( - - ) + ); } render() { const { isFetching, nextPageUrl, pageCount, - items, renderItem, loadingLabel - } = this.props + items, renderItem, loadingLabel, + } = this.props; - const isEmpty = items.length === 0 + const isEmpty = items.length === 0; if (isEmpty && isFetching) { - return

{loadingLabel}

+ return

{loadingLabel}

; } - const isLastPage = !nextPageUrl + const isLastPage = !nextPageUrl; if (isEmpty && isLastPage) { - return

Nothing here!

+ return

Nothing here!

; } return ( @@ -33,7 +31,7 @@ export default class List extends Component { {items.map(renderItem)} {pageCount > 0 && !isLastPage && this.renderLoadMore()}
- ) + ); } } @@ -44,10 +42,10 @@ List.propTypes = { items: PropTypes.array.isRequired, isFetching: PropTypes.bool.isRequired, onLoadMoreClick: PropTypes.func.isRequired, - nextPageUrl: PropTypes.string -} + nextPageUrl: PropTypes.string, +}; List.defaultProps = { isFetching: true, - loadingLabel: 'Loading...' -} + loadingLabel: 'Loading...', +}; diff --git a/examples/real-world/components/Repo.js b/examples/real-world/components/Repo.js index a9754e110f..7503bf96c2 100644 --- a/examples/real-world/components/Repo.js +++ b/examples/real-world/components/Repo.js @@ -1,38 +1,36 @@ -import React, { Component, PropTypes } from 'react' -import { Link } from 'react-router' +import React, { PropTypes } from 'react'; +import { Link } from 'react-router'; -export default class Repo extends Component { +const Repo = ({ repo, owner }) => { + const { login } = owner; + const { name, description } = repo; - render() { - const { repo, owner } = this.props - const { login } = owner - const { name, description } = repo - - return ( -
-

- - {name} - - {' by '} - - {login} - -

- {description && -

{description}

- } -
- ) - } -} + return ( +
+

+ + {name} + + {' by '} + + {login} + +

+ {description && +

{description}

+ } +
+ ); +}; Repo.propTypes = { repo: PropTypes.shape({ name: PropTypes.string.isRequired, - description: PropTypes.string + description: PropTypes.string, }).isRequired, owner: PropTypes.shape({ - login: PropTypes.string.isRequired - }).isRequired -} + login: PropTypes.string.isRequired, + }).isRequired, +}; + +export default Repo; diff --git a/examples/real-world/components/User.js b/examples/real-world/components/User.js index c52ade2a3a..f7a8086c56 100644 --- a/examples/real-world/components/User.js +++ b/examples/real-world/components/User.js @@ -1,27 +1,27 @@ -import React, { Component, PropTypes } from 'react' -import { Link } from 'react-router' +import React, { PropTypes } from 'react'; +import { Link } from 'react-router'; -export default class User extends Component { - render() { - const { login, avatarUrl, name } = this.props.user +const User = ({ user }) => { + const { login, avatarUrl, name } = user; - return ( -
- - -

- {login} {name && ({name})} -

- -
- ) - } -} + return ( +
+ + +

+ {login} {name && ({name})} +

+ +
+ ); +}; User.propTypes = { user: PropTypes.shape({ login: PropTypes.string.isRequired, avatarUrl: PropTypes.string.isRequired, - name: PropTypes.string - }).isRequired -} + name: PropTypes.string, + }).isRequired, +}; + +export default User; diff --git a/examples/real-world/containers/App.js b/examples/real-world/containers/App.js index f7db3cd2b8..545f2665bf 100644 --- a/examples/real-world/containers/App.js +++ b/examples/real-world/containers/App.js @@ -1,29 +1,29 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import { browserHistory } from 'react-router' -import Explore from '../components/Explore' -import { resetErrorMessage } from '../actions' +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { browserHistory } from 'react-router'; +import Explore from '../components/Explore'; +import { resetErrorMessage } from '../actions'; class App extends Component { constructor(props) { - super(props) - this.handleChange = this.handleChange.bind(this) - this.handleDismissClick = this.handleDismissClick.bind(this) + super(props); + this.handleChange = this.handleChange.bind(this); + this.handleDismissClick = this.handleDismissClick.bind(this); } handleDismissClick(e) { - this.props.resetErrorMessage() - e.preventDefault() + this.props.resetErrorMessage(); + e.preventDefault(); } handleChange(nextValue) { - browserHistory.push(`/${nextValue}`) + browserHistory.push(`/${nextValue}`); } renderErrorMessage() { - const { errorMessage } = this.props + const { errorMessage } = this.props; if (!errorMessage) { - return null + return null; } return ( @@ -31,24 +31,26 @@ class App extends Component { {errorMessage} {' '} ( + onClick={this.handleDismissClick} + > Dismiss )

- ) + ); } render() { - const { children, inputValue } = this.props + const { children, inputValue } = this.props; return (
+ onChange={this.handleChange} + />
{this.renderErrorMessage()} {children}
- ) + ); } } @@ -58,16 +60,16 @@ App.propTypes = { resetErrorMessage: PropTypes.func.isRequired, inputValue: PropTypes.string.isRequired, // Injected by React Router - children: PropTypes.node -} + children: PropTypes.node, +}; function mapStateToProps(state, ownProps) { return { errorMessage: state.errorMessage, - inputValue: ownProps.location.pathname.substring(1) - } + inputValue: ownProps.location.pathname.substring(1), + }; } export default connect(mapStateToProps, { - resetErrorMessage -})(App) + resetErrorMessage, +})(App); diff --git a/examples/real-world/containers/DevTools.js b/examples/real-world/containers/DevTools.js index ad1b6fdc71..caee0a21eb 100644 --- a/examples/real-world/containers/DevTools.js +++ b/examples/real-world/containers/DevTools.js @@ -1,11 +1,10 @@ -import React from 'react' -import { createDevTools } from 'redux-devtools' -import LogMonitor from 'redux-devtools-log-monitor' -import DockMonitor from 'redux-devtools-dock-monitor' +import React from 'react'; +import { createDevTools } from 'redux-devtools'; +import LogMonitor from 'redux-devtools-log-monitor'; +import DockMonitor from 'redux-devtools-dock-monitor'; export default createDevTools( - + -) +); diff --git a/examples/real-world/containers/RepoPage.js b/examples/real-world/containers/RepoPage.js index e5e62c7c30..9f03d49110 100644 --- a/examples/real-world/containers/RepoPage.js +++ b/examples/real-world/containers/RepoPage.js @@ -1,63 +1,66 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import { loadRepo, loadStargazers } from '../actions' -import Repo from '../components/Repo' -import User from '../components/User' -import List from '../components/List' +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { loadRepo, loadStargazers } from '../actions'; +import Repo from '../components/Repo'; +import User from '../components/User'; +import List from '../components/List'; function loadData(props) { - const { fullName } = props - props.loadRepo(fullName, [ 'description' ]) - props.loadStargazers(fullName) + const { fullName } = props; + props.loadRepo(fullName, ['description']); + props.loadStargazers(fullName); } class RepoPage extends Component { constructor(props) { - super(props) - this.renderUser = this.renderUser.bind(this) - this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this) + super(props); + this.renderUser = this.renderUser.bind(this); + this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this); } componentWillMount() { - loadData(this.props) + loadData(this.props); } componentWillReceiveProps(nextProps) { if (nextProps.fullName !== this.props.fullName) { - loadData(nextProps) + loadData(nextProps); } } handleLoadMoreClick() { - this.props.loadStargazers(this.props.fullName, true) + this.props.loadStargazers(this.props.fullName, true); } renderUser(user) { return ( - ) + key={user.login} + /> + ); } render() { - const { repo, owner, name } = this.props + const { repo, owner, name } = this.props; if (!repo || !owner) { - return

Loading {name} details...

+ return

Loading {name} details...

; } - const { stargazers, stargazersPagination } = this.props + const { stargazers, stargazersPagination } = this.props; return (
+ owner={owner} + />
+ items={stargazers} + onLoadMoreClick={this.handleLoadMoreClick} + loadingLabel={`Loading stargazers of ${name}...`} + {...stargazersPagination} + />
- ) + ); } } @@ -69,19 +72,19 @@ RepoPage.propTypes = { stargazers: PropTypes.array.isRequired, stargazersPagination: PropTypes.object, loadRepo: PropTypes.func.isRequired, - loadStargazers: PropTypes.func.isRequired -} + loadStargazers: PropTypes.func.isRequired, +}; function mapStateToProps(state, ownProps) { - const { login, name } = ownProps.params + const { login, name } = ownProps.params; const { pagination: { stargazersByRepo }, - entities: { users, repos } - } = state + entities: { users, repos }, + } = state; - const fullName = `${login}/${name}` - const stargazersPagination = stargazersByRepo[fullName] || { ids: [] } - const stargazers = stargazersPagination.ids.map(id => users[id]) + const fullName = `${login}/${name}`; + const stargazersPagination = stargazersByRepo[fullName] || { ids: [] }; + const stargazers = stargazersPagination.ids.map(id => users[id]); return { fullName, @@ -89,11 +92,11 @@ function mapStateToProps(state, ownProps) { stargazers, stargazersPagination, repo: repos[fullName], - owner: users[login] - } + owner: users[login], + }; } export default connect(mapStateToProps, { loadRepo, - loadStargazers -})(RepoPage) + loadStargazers, +})(RepoPage); diff --git a/examples/real-world/containers/Root.dev.js b/examples/real-world/containers/Root.dev.js index 8645b5c60c..d8e98bd930 100644 --- a/examples/real-world/containers/Root.dev.js +++ b/examples/real-world/containers/Root.dev.js @@ -1,24 +1,21 @@ -import React, { Component, PropTypes } from 'react' -import { Provider } from 'react-redux' -import routes from '../routes' -import DevTools from './DevTools' -import { Router } from 'react-router' +import React, { PropTypes } from 'react'; +import { Provider } from 'react-redux'; +import routes from '../routes'; +import DevTools from './DevTools'; +import { Router } from 'react-router'; -export default class Root extends Component { - render() { - const { store, history } = this.props - return ( - -
- - -
-
- ) - } -} +const Root = ({ store, history }) => ( + +
+ + +
+
+); Root.propTypes = { store: PropTypes.object.isRequired, - history: PropTypes.object.isRequired -} + history: PropTypes.object.isRequired, +}; + +export default Root; diff --git a/examples/real-world/containers/Root.js b/examples/real-world/containers/Root.js index 47900aa892..9cea0b2263 100644 --- a/examples/real-world/containers/Root.js +++ b/examples/real-world/containers/Root.js @@ -1,5 +1,5 @@ if (process.env.NODE_ENV === 'production') { - module.exports = require('./Root.prod') + module.exports = require('./Root.prod'); } else { - module.exports = require('./Root.dev') + module.exports = require('./Root.dev'); } diff --git a/examples/real-world/containers/Root.prod.js b/examples/real-world/containers/Root.prod.js index bb7a16eb13..a73e45369a 100644 --- a/examples/real-world/containers/Root.prod.js +++ b/examples/real-world/containers/Root.prod.js @@ -1,20 +1,17 @@ -import React, { Component, PropTypes } from 'react' -import { Provider } from 'react-redux' -import routes from '../routes' -import { Router } from 'react-router' +import React, { PropTypes } from 'react'; +import { Provider } from 'react-redux'; +import routes from '../routes'; +import { Router } from 'react-router'; -export default class Root extends Component { - render() { - const { store, history } = this.props - return ( - - - - ) - } -} +const Root = ({ store, history }) => ( + + + +); Root.propTypes = { store: PropTypes.object.isRequired, - history: PropTypes.object.isRequired -} + history: PropTypes.object.isRequired, +}; + +export default Root; diff --git a/examples/real-world/containers/UserPage.js b/examples/real-world/containers/UserPage.js index 29a25f0220..55fba689bf 100644 --- a/examples/real-world/containers/UserPage.js +++ b/examples/real-world/containers/UserPage.js @@ -1,64 +1,66 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import { loadUser, loadStarred } from '../actions' -import User from '../components/User' -import Repo from '../components/Repo' -import List from '../components/List' -import zip from 'lodash/zip' +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { loadUser, loadStarred } from '../actions'; +import User from '../components/User'; +import Repo from '../components/Repo'; +import List from '../components/List'; +import zip from 'lodash/zip'; function loadData(props) { - const { login } = props - props.loadUser(login, [ 'name' ]) - props.loadStarred(login) + const { login } = props; + props.loadUser(login, ['name']); + props.loadStarred(login); } class UserPage extends Component { constructor(props) { - super(props) - this.renderRepo = this.renderRepo.bind(this) - this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this) + super(props); + this.renderRepo = this.renderRepo.bind(this); + this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this); } componentWillMount() { - loadData(this.props) + loadData(this.props); } componentWillReceiveProps(nextProps) { if (nextProps.login !== this.props.login) { - loadData(nextProps) + loadData(nextProps); } } handleLoadMoreClick() { - this.props.loadStarred(this.props.login, true) + this.props.loadStarred(this.props.login, true); } - renderRepo([ repo, owner ]) { + renderRepo([repo, owner]) { return ( - ) + owner={owner} + key={repo.fullName} + /> + ); } render() { - const { user, login } = this.props + const { user, login } = this.props; if (!user) { - return

Loading {login}’s profile...

+ return

Loading {login}’s profile...

; } - const { starredRepos, starredRepoOwners, starredPagination } = this.props + const { starredRepos, starredRepoOwners, starredPagination } = this.props; return (

+ items={zip(starredRepos, starredRepoOwners)} + onLoadMoreClick={this.handleLoadMoreClick} + loadingLabel={`Loading ${login}’s starred...`} + {...starredPagination} + />
- ) + ); } } @@ -69,30 +71,30 @@ UserPage.propTypes = { starredRepos: PropTypes.array.isRequired, starredRepoOwners: PropTypes.array.isRequired, loadUser: PropTypes.func.isRequired, - loadStarred: PropTypes.func.isRequired -} + loadStarred: PropTypes.func.isRequired, +}; function mapStateToProps(state, ownProps) { - const { login } = ownProps.params + const { login } = ownProps.params; const { pagination: { starredByUser }, - entities: { users, repos } - } = state + entities: { users, repos }, + } = state; - const starredPagination = starredByUser[login] || { ids: [] } - const starredRepos = starredPagination.ids.map(id => repos[id]) - const starredRepoOwners = starredRepos.map(repo => users[repo.owner]) + const starredPagination = starredByUser[login] || { ids: [] }; + const starredRepos = starredPagination.ids.map(id => repos[id]); + const starredRepoOwners = starredRepos.map(repo => users[repo.owner]); return { login, starredRepos, starredRepoOwners, starredPagination, - user: users[login] - } + user: users[login], + }; } export default connect(mapStateToProps, { loadUser, - loadStarred -})(UserPage) + loadStarred, +})(UserPage); diff --git a/examples/real-world/index.js b/examples/real-world/index.js index aacfee6fb5..bd63cf5e2c 100644 --- a/examples/real-world/index.js +++ b/examples/real-world/index.js @@ -1,15 +1,15 @@ -import 'babel-polyfill' -import React from 'react' -import { render } from 'react-dom' -import { browserHistory } from 'react-router' -import { syncHistoryWithStore } from 'react-router-redux' -import Root from './containers/Root' -import configureStore from './store/configureStore' +import 'babel-polyfill'; +import React from 'react'; +import { render } from 'react-dom'; +import { browserHistory } from 'react-router'; +import { syncHistoryWithStore } from 'react-router-redux'; +import Root from './containers/Root'; +import configureStore from './store/configureStore'; -const store = configureStore() -const history = syncHistoryWithStore(browserHistory, store) +const store = configureStore(); +const history = syncHistoryWithStore(browserHistory, store); render( , document.getElementById('root') -) +); diff --git a/examples/real-world/middleware/api.js b/examples/real-world/middleware/api.js index d7ae44a589..640166e590 100644 --- a/examples/real-world/middleware/api.js +++ b/examples/real-world/middleware/api.js @@ -1,45 +1,45 @@ -import { Schema, arrayOf, normalize } from 'normalizr' -import { camelizeKeys } from 'humps' -import 'isomorphic-fetch' +import { Schema, arrayOf, normalize } from 'normalizr'; +import { camelizeKeys } from 'humps'; +import 'isomorphic-fetch'; // Extracts the next page URL from Github API response. function getNextPageUrl(response) { - const link = response.headers.get('link') + const link = response.headers.get('link'); if (!link) { - return null + return null; } - const nextLink = link.split(',').find(s => s.indexOf('rel="next"') > -1) + const nextLink = link.split(',').find(s => s.indexOf('rel="next"') > -1); if (!nextLink) { - return null + return null; } - return nextLink.split(';')[0].slice(1, -1) + return nextLink.split(';')[0].slice(1, -1); } -const API_ROOT = 'https://api.github.com/' +const API_ROOT = 'https://api.github.com/'; // Fetches an API response and normalizes the result JSON according to schema. // This makes every API response have the same shape, regardless of how nested it was. function callApi(endpoint, schema) { - const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint + const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint; return fetch(fullUrl) .then(response => response.json().then(json => ({ json, response })) ).then(({ json, response }) => { if (!response.ok) { - return Promise.reject(json) + return Promise.reject(json); } - const camelizedJson = camelizeKeys(json) - const nextPageUrl = getNextPageUrl(response) + const camelizedJson = camelizeKeys(json); + const nextPageUrl = getNextPageUrl(response); return Object.assign({}, normalize(camelizedJson, schema), { nextPageUrl } - ) - }) + ); + }); } // We use this Normalizr schemas to transform API responses from a nested form @@ -51,73 +51,73 @@ function callApi(endpoint, schema) { // Read more about Normalizr: https://github.com/gaearon/normalizr const userSchema = new Schema('users', { - idAttribute: 'login' -}) + idAttribute: 'login', +}); const repoSchema = new Schema('repos', { - idAttribute: 'fullName' -}) + idAttribute: 'fullName', +}); repoSchema.define({ - owner: userSchema -}) + owner: userSchema, +}); // Schemas for Github API responses. export const Schemas = { USER: userSchema, USER_ARRAY: arrayOf(userSchema), REPO: repoSchema, - REPO_ARRAY: arrayOf(repoSchema) -} + REPO_ARRAY: arrayOf(repoSchema), +}; // Action key that carries API call info interpreted by this Redux middleware. -export const CALL_API = Symbol('Call API') +export const CALL_API = Symbol('Call API'); // A Redux middleware that interprets actions with CALL_API info specified. // Performs the call and promises when such actions are dispatched. export default store => next => action => { - const callAPI = action[CALL_API] + const callAPI = action[CALL_API]; if (typeof callAPI === 'undefined') { - return next(action) + return next(action); } - let { endpoint } = callAPI - const { schema, types } = callAPI + let { endpoint } = callAPI; + const { schema, types } = callAPI; if (typeof endpoint === 'function') { - endpoint = endpoint(store.getState()) + endpoint = endpoint(store.getState()); } if (typeof endpoint !== 'string') { - throw new Error('Specify a string endpoint URL.') + throw new Error('Specify a string endpoint URL.'); } if (!schema) { - throw new Error('Specify one of the exported Schemas.') + throw new Error('Specify one of the exported Schemas.'); } if (!Array.isArray(types) || types.length !== 3) { - throw new Error('Expected an array of three action types.') + throw new Error('Expected an array of three action types.'); } if (!types.every(type => typeof type === 'string')) { - throw new Error('Expected action types to be strings.') + throw new Error('Expected action types to be strings.'); } function actionWith(data) { - const finalAction = Object.assign({}, action, data) - delete finalAction[CALL_API] - return finalAction + const finalAction = Object.assign({}, action, data); + delete finalAction[CALL_API]; + return finalAction; } - const [ requestType, successType, failureType ] = types - next(actionWith({ type: requestType })) + const [requestType, successType, failureType] = types; + next(actionWith({ type: requestType })); return callApi(endpoint, schema).then( response => next(actionWith({ response, - type: successType + type: successType, })), error => next(actionWith({ type: failureType, - error: error.message || 'Something bad happened' + error: error.message || 'Something bad happened', })) - ) -} + ); +}; diff --git a/examples/real-world/reducers/index.js b/examples/real-world/reducers/index.js index 8dfda6b88a..706f9e1b7f 100644 --- a/examples/real-world/reducers/index.js +++ b/examples/real-world/reducers/index.js @@ -1,29 +1,29 @@ -import * as ActionTypes from '../actions' -import merge from 'lodash/merge' -import paginate from './paginate' -import { routerReducer as routing } from 'react-router-redux' -import { combineReducers } from 'redux' +import * as ActionTypes from '../actions'; +import merge from 'lodash/merge'; +import paginate from './paginate'; +import { routerReducer as routing } from 'react-router-redux'; +import { combineReducers } from 'redux'; // Updates an entity cache in response to any action with response.entities. function entities(state = { users: {}, repos: {} }, action) { if (action.response && action.response.entities) { - return merge({}, state, action.response.entities) + return merge({}, state, action.response.entities); } - return state + return state; } // Updates error message to notify about the failed fetches. function errorMessage(state = null, action) { - const { type, error } = action + const { type, error } = action; if (type === ActionTypes.RESET_ERROR_MESSAGE) { - return null + return null; } else if (error) { - return action.error + return action.error; } - return state + return state; } // Updates the pagination data for different actions. @@ -33,24 +33,24 @@ const pagination = combineReducers({ types: [ ActionTypes.STARRED_REQUEST, ActionTypes.STARRED_SUCCESS, - ActionTypes.STARRED_FAILURE - ] + ActionTypes.STARRED_FAILURE, + ], }), stargazersByRepo: paginate({ mapActionToKey: action => action.fullName, types: [ ActionTypes.STARGAZERS_REQUEST, ActionTypes.STARGAZERS_SUCCESS, - ActionTypes.STARGAZERS_FAILURE - ] - }) -}) + ActionTypes.STARGAZERS_FAILURE, + ], + }), +}); const rootReducer = combineReducers({ entities, pagination, errorMessage, - routing -}) + routing, +}); -export default rootReducer +export default rootReducer; diff --git a/examples/real-world/reducers/paginate.js b/examples/real-world/reducers/paginate.js index 53d5dcc053..bd8a3b4704 100644 --- a/examples/real-world/reducers/paginate.js +++ b/examples/real-world/reducers/paginate.js @@ -1,45 +1,45 @@ -import merge from 'lodash/merge' -import union from 'lodash/union' +import merge from 'lodash/merge'; +import union from 'lodash/union'; // Creates a reducer managing pagination, given the action types to handle, // and a function telling how to extract the key from an action. export default function paginate({ types, mapActionToKey }) { if (!Array.isArray(types) || types.length !== 3) { - throw new Error('Expected types to be an array of three elements.') + throw new Error('Expected types to be an array of three elements.'); } if (!types.every(t => typeof t === 'string')) { - throw new Error('Expected types to be strings.') + throw new Error('Expected types to be strings.'); } if (typeof mapActionToKey !== 'function') { - throw new Error('Expected mapActionToKey to be a function.') + throw new Error('Expected mapActionToKey to be a function.'); } - const [ requestType, successType, failureType ] = types + const [requestType, successType, failureType] = types; function updatePagination(state = { isFetching: false, nextPageUrl: undefined, pageCount: 0, - ids: [] + ids: [], }, action) { switch (action.type) { case requestType: return merge({}, state, { - isFetching: true - }) + isFetching: true, + }); case successType: return merge({}, state, { isFetching: false, ids: union(state.ids, action.response.result), nextPageUrl: action.response.nextPageUrl, - pageCount: state.pageCount + 1 - }) + pageCount: state.pageCount + 1, + }); case failureType: return merge({}, state, { - isFetching: false - }) + isFetching: false, + }); default: - return state + return state; } } @@ -47,16 +47,17 @@ export default function paginate({ types, mapActionToKey }) { switch (action.type) { case requestType: case successType: - case failureType: - const key = mapActionToKey(action) + case failureType: { + const key = mapActionToKey(action); if (typeof key !== 'string') { - throw new Error('Expected key to be a string.') + throw new Error('Expected key to be a string.'); } return merge({}, state, { - [key]: updatePagination(state[key], action) - }) + [key]: updatePagination(state[key], action), + }); + } default: - return state + return state; } - } + }; } diff --git a/examples/real-world/routes.js b/examples/real-world/routes.js index 899ff73f89..46ea6fd1fc 100644 --- a/examples/real-world/routes.js +++ b/examples/real-world/routes.js @@ -1,14 +1,12 @@ -import React from 'react' -import { Route } from 'react-router' -import App from './containers/App' -import UserPage from './containers/UserPage' -import RepoPage from './containers/RepoPage' +import React from 'react'; +import { Route } from 'react-router'; +import App from './containers/App'; +import UserPage from './containers/UserPage'; +import RepoPage from './containers/RepoPage'; export default ( - - + + -) +); diff --git a/examples/real-world/store/configureStore.dev.js b/examples/real-world/store/configureStore.dev.js index c081a7b347..4763fb1e98 100644 --- a/examples/real-world/store/configureStore.dev.js +++ b/examples/real-world/store/configureStore.dev.js @@ -1,9 +1,9 @@ -import { createStore, applyMiddleware, compose } from 'redux' -import thunk from 'redux-thunk' -import createLogger from 'redux-logger' -import api from '../middleware/api' -import rootReducer from '../reducers' -import DevTools from '../containers/DevTools' +import { createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; +import createLogger from 'redux-logger'; +import api from '../middleware/api'; +import rootReducer from '../reducers'; +import DevTools from '../containers/DevTools'; export default function configureStore(initialState) { const store = createStore( @@ -13,15 +13,15 @@ export default function configureStore(initialState) { applyMiddleware(thunk, api, createLogger()), DevTools.instrument() ) - ) + ); if (module.hot) { // Enable Webpack hot module replacement for reducers module.hot.accept('../reducers', () => { - const nextRootReducer = require('../reducers').default - store.replaceReducer(nextRootReducer) - }) + const nextRootReducer = require('../reducers').default; + store.replaceReducer(nextRootReducer); + }); } - return store + return store; } diff --git a/examples/real-world/store/configureStore.js b/examples/real-world/store/configureStore.js index a4c9e7ad42..78c9ea1fdc 100644 --- a/examples/real-world/store/configureStore.js +++ b/examples/real-world/store/configureStore.js @@ -1,5 +1,5 @@ if (process.env.NODE_ENV === 'production') { - module.exports = require('./configureStore.prod') + module.exports = require('./configureStore.prod'); } else { - module.exports = require('./configureStore.dev') + module.exports = require('./configureStore.dev'); } diff --git a/examples/real-world/store/configureStore.prod.js b/examples/real-world/store/configureStore.prod.js index 3a9b853caf..e5b673ab24 100644 --- a/examples/real-world/store/configureStore.prod.js +++ b/examples/real-world/store/configureStore.prod.js @@ -1,12 +1,12 @@ -import { createStore, applyMiddleware } from 'redux' -import thunk from 'redux-thunk' -import api from '../middleware/api' -import rootReducer from '../reducers' +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; +import api from '../middleware/api'; +import rootReducer from '../reducers'; export default function configureStore(initialState) { return createStore( rootReducer, initialState, applyMiddleware(thunk, api) - ) + ); } diff --git a/examples/shopping-cart/actions/index.js b/examples/shopping-cart/actions/index.js index 5031fce707..b3b0da59d3 100644 --- a/examples/shopping-cart/actions/index.js +++ b/examples/shopping-cart/actions/index.js @@ -1,50 +1,50 @@ -import shop from '../api/shop' -import * as types from '../constants/ActionTypes' +import shop from '../api/shop'; +import * as types from '../constants/ActionTypes'; function receiveProducts(products) { return { type: types.RECEIVE_PRODUCTS, - products: products - } + products, + }; } export function getAllProducts() { return dispatch => { shop.getProducts(products => { - dispatch(receiveProducts(products)) - }) - } + dispatch(receiveProducts(products)); + }); + }; } function addToCartUnsafe(productId) { return { type: types.ADD_TO_CART, - productId - } + productId, + }; } export function addToCart(productId) { return (dispatch, getState) => { if (getState().products.byId[productId].inventory > 0) { - dispatch(addToCartUnsafe(productId)) + dispatch(addToCartUnsafe(productId)); } - } + }; } export function checkout(products) { return (dispatch, getState) => { - const cart = getState().cart + const cart = getState().cart; dispatch({ - type: types.CHECKOUT_REQUEST - }) + type: types.CHECKOUT_REQUEST, + }); shop.buyProducts(products, () => { dispatch({ type: types.CHECKOUT_SUCCESS, - cart - }) + cart, + }); // Replace the line above with line below to rollback on failure: // dispatch({ type: types.CHECKOUT_FAILURE, cart }) - }) - } + }); + }; } diff --git a/examples/shopping-cart/api/shop.js b/examples/shopping-cart/api/shop.js index def5958a1c..c9c32481f8 100644 --- a/examples/shopping-cart/api/shop.js +++ b/examples/shopping-cart/api/shop.js @@ -1,16 +1,16 @@ /** * Mocking client-server processing */ -import _products from './products.json' +import _products from './products.json'; -const TIMEOUT = 100 +const TIMEOUT = 100; export default { getProducts(cb, timeout) { - setTimeout(() => cb(_products), timeout || TIMEOUT) + setTimeout(() => cb(_products), timeout || TIMEOUT); }, buyProducts(payload, cb, timeout) { - setTimeout(() => cb(), timeout || TIMEOUT) - } -} + setTimeout(() => cb(), timeout || TIMEOUT); + }, +}; diff --git a/examples/shopping-cart/components/Cart.js b/examples/shopping-cart/components/Cart.js index 1fc47cedfa..73c04336fd 100644 --- a/examples/shopping-cart/components/Cart.js +++ b/examples/shopping-cart/components/Cart.js @@ -1,37 +1,35 @@ -import React, { Component, PropTypes } from 'react' -import Product from './Product' +import React, { PropTypes } from 'react'; +import Product from './Product'; -export default class Cart extends Component { - render() { - const { products, total, onCheckoutClicked } = this.props +const Cart = ({ products, total, onCheckoutClicked }) => { + const hasProducts = products.length > 0; + const nodes = !hasProducts ? + Please add some products to cart. : + products.map(product => + + ); - const hasProducts = products.length > 0 - const nodes = !hasProducts ? - Please add some products to cart. : - products.map(product => - - ) - - return ( -
-

Your Cart

-
{nodes}
-

Total: ${total}

- -
- ) - } -} + return ( +
+

Your Cart

+
{nodes}
+

Total: ${total}

+ +
+ ); +}; Cart.propTypes = { products: PropTypes.array, total: PropTypes.string, - onCheckoutClicked: PropTypes.func -} + onCheckoutClicked: PropTypes.func, +}; + +export default Cart; diff --git a/examples/shopping-cart/components/Product.js b/examples/shopping-cart/components/Product.js index dfa257b7e5..e0a9f592ae 100644 --- a/examples/shopping-cart/components/Product.js +++ b/examples/shopping-cart/components/Product.js @@ -1,14 +1,13 @@ -import React, { Component, PropTypes } from 'react' +import React, { PropTypes } from 'react'; -export default class Product extends Component { - render() { - const { price, quantity, title } = this.props - return
{title} - ${price} {quantity ? `x ${quantity}` : null}
- } -} +const Product = ({ price, quantity, title }) => ( +
{title} - ${price} {quantity ? `x ${quantity}` : null}
+); Product.propTypes = { price: PropTypes.number, quantity: PropTypes.number, - title: PropTypes.string -} + title: PropTypes.string, +}; + +export default Product; diff --git a/examples/shopping-cart/components/ProductItem.js b/examples/shopping-cart/components/ProductItem.js index c2f58eb235..c473323159 100644 --- a/examples/shopping-cart/components/ProductItem.js +++ b/examples/shopping-cart/components/ProductItem.js @@ -1,31 +1,25 @@ -import React, { Component, PropTypes } from 'react' -import Product from './Product' +import React, { PropTypes } from 'react'; +import Product from './Product'; -export default class ProductItem extends Component { - render() { - const { product } = this.props - - return ( -
- - -
- ) - } -} +const ProductItem = ({ product, onAddToCartClicked }) => ( +
+ + +
+); ProductItem.propTypes = { product: PropTypes.shape({ title: PropTypes.string.isRequired, price: PropTypes.number.isRequired, - inventory: PropTypes.number.isRequired + inventory: PropTypes.number.isRequired, }).isRequired, - onAddToCartClicked: PropTypes.func.isRequired -} + onAddToCartClicked: PropTypes.func.isRequired, +}; + +export default ProductItem; diff --git a/examples/shopping-cart/components/ProductsList.js b/examples/shopping-cart/components/ProductsList.js index 086e529bb8..e437051772 100644 --- a/examples/shopping-cart/components/ProductsList.js +++ b/examples/shopping-cart/components/ProductsList.js @@ -1,17 +1,15 @@ -import React, { Component, PropTypes } from 'react' +import React, { PropTypes } from 'react'; -export default class ProductsList extends Component { - render() { - return ( -
-

{this.props.title}

-
{this.props.children}
-
- ) - } -} +const ProductsList = ({ title, children }) => ( +
+

{title}

+
{children}
+
+); ProductsList.propTypes = { children: PropTypes.node, - title: PropTypes.string.isRequired -} + title: PropTypes.string.isRequired, +}; + +export default ProductsList; diff --git a/examples/shopping-cart/constants/ActionTypes.js b/examples/shopping-cart/constants/ActionTypes.js index 6cbabea97c..2f6312297f 100644 --- a/examples/shopping-cart/constants/ActionTypes.js +++ b/examples/shopping-cart/constants/ActionTypes.js @@ -1,5 +1,5 @@ -export const ADD_TO_CART = 'ADD_TO_CART' -export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST' -export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS' -export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE' -export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS' +export const ADD_TO_CART = 'ADD_TO_CART'; +export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST'; +export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS'; +export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE'; +export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'; diff --git a/examples/shopping-cart/containers/App.js b/examples/shopping-cart/containers/App.js index f19cba3d14..89d300bf37 100644 --- a/examples/shopping-cart/containers/App.js +++ b/examples/shopping-cart/containers/App.js @@ -1,17 +1,15 @@ -import React, { Component } from 'react' -import ProductsContainer from './ProductsContainer' -import CartContainer from './CartContainer' +import React from 'react'; +import ProductsContainer from './ProductsContainer'; +import CartContainer from './CartContainer'; -export default class App extends Component { - render() { - return ( -
-

Shopping Cart Example

-
- -
- -
- ) - } -} +const App = () => ( +
+

Shopping Cart Example

+
+ +
+ +
+); + +export default App; diff --git a/examples/shopping-cart/containers/CartContainer.js b/examples/shopping-cart/containers/CartContainer.js index 45d5d21ccc..3eaab92ba8 100644 --- a/examples/shopping-cart/containers/CartContainer.js +++ b/examples/shopping-cart/containers/CartContainer.js @@ -1,41 +1,40 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import { checkout } from '../actions' -import { getTotal, getCartProducts } from '../reducers' -import Cart from '../components/Cart' +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { checkout } from '../actions'; +import { getTotal, getCartProducts } from '../reducers'; +import Cart from '../components/Cart'; -class CartContainer extends Component { - render() { - const { products, total } = this.props +const CartContainer = (props) => { + const { products, total } = props; - return ( - this.props.checkout()} /> - ) - } -} + return ( + props.checkout()} + /> + ); +}; CartContainer.propTypes = { products: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, price: PropTypes.number.isRequired, - quantity: PropTypes.number.isRequired + quantity: PropTypes.number.isRequired, })).isRequired, total: PropTypes.string, - checkout: PropTypes.func.isRequired -} + checkout: PropTypes.func.isRequired, +}; -const mapStateToProps = (state) => { - return { +const mapStateToProps = (state) => ( + { products: getCartProducts(state), - total: getTotal(state) + total: getTotal(state), } -} +); export default connect( mapStateToProps, { checkout } -)(CartContainer) +)(CartContainer); diff --git a/examples/shopping-cart/containers/ProductsContainer.js b/examples/shopping-cart/containers/ProductsContainer.js index 154982c201..98f074c3d8 100644 --- a/examples/shopping-cart/containers/ProductsContainer.js +++ b/examples/shopping-cart/containers/ProductsContainer.js @@ -1,43 +1,42 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import { addToCart } from '../actions' -import { getVisibleProducts } from '../reducers/products' -import ProductItem from '../components/ProductItem' -import ProductsList from '../components/ProductsList' +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { addToCart } from '../actions'; +import { getVisibleProducts } from '../reducers/products'; +import ProductItem from '../components/ProductItem'; +import ProductsList from '../components/ProductsList'; -class ProductsContainer extends Component { - render() { - const { products } = this.props - return ( - - {products.map(product => - this.props.addToCart(product.id)} /> - )} - - ) - } -} +const ProductsContainer = (props) => { + const { products } = props; + return ( + + {products.map(product => + props.addToCart(product.id)} + /> + )} + + ); +}; ProductsContainer.propTypes = { products: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, price: PropTypes.number.isRequired, - inventory: PropTypes.number.isRequired + inventory: PropTypes.number.isRequired, })).isRequired, - addToCart: PropTypes.func.isRequired -} + addToCart: PropTypes.func.isRequired, +}; function mapStateToProps(state) { return { - products: getVisibleProducts(state.products) - } + products: getVisibleProducts(state.products), + }; } export default connect( mapStateToProps, { addToCart } -)(ProductsContainer) +)(ProductsContainer); diff --git a/examples/shopping-cart/index.js b/examples/shopping-cart/index.js index 1b475e4714..bd56cca6ab 100644 --- a/examples/shopping-cart/index.js +++ b/examples/shopping-cart/index.js @@ -1,28 +1,28 @@ -import 'babel-polyfill' -import React from 'react' -import { render } from 'react-dom' -import { createStore, applyMiddleware } from 'redux' -import { Provider } from 'react-redux' -import logger from 'redux-logger' -import thunk from 'redux-thunk' -import reducer from './reducers' -import { getAllProducts } from './actions' -import App from './containers/App' +import 'babel-polyfill'; +import React from 'react'; +import { render } from 'react-dom'; +import { createStore, applyMiddleware } from 'redux'; +import { Provider } from 'react-redux'; +import logger from 'redux-logger'; +import thunk from 'redux-thunk'; +import reducer from './reducers'; +import { getAllProducts } from './actions'; +import App from './containers/App'; const middleware = process.env.NODE_ENV === 'production' ? - [ thunk ] : - [ thunk, logger() ] + [thunk] : + [thunk, logger()]; const store = createStore( reducer, applyMiddleware(...middleware) -) +); -store.dispatch(getAllProducts()) +store.dispatch(getAllProducts()); render( , document.getElementById('root') -) +); diff --git a/examples/shopping-cart/reducers/cart.js b/examples/shopping-cart/reducers/cart.js index 39725821ef..f4d517e016 100644 --- a/examples/shopping-cart/reducers/cart.js +++ b/examples/shopping-cart/reducers/cart.js @@ -1,56 +1,57 @@ import { ADD_TO_CART, CHECKOUT_REQUEST, - CHECKOUT_FAILURE -} from '../constants/ActionTypes' + CHECKOUT_FAILURE, +} from '../constants/ActionTypes'; const initialState = { addedIds: [], - quantityById: {} -} + quantityById: {}, +}; function addedIds(state = initialState.addedIds, action) { switch (action.type) { case ADD_TO_CART: if (state.indexOf(action.productId) !== -1) { - return state + return state; } - return [ ...state, action.productId ] + return [...state, action.productId]; default: - return state + return state; } } function quantityById(state = initialState.quantityById, action) { switch (action.type) { - case ADD_TO_CART: - const { productId } = action + case ADD_TO_CART: { + const { productId } = action; return Object.assign({}, state, { - [productId]: (state[productId] || 0) + 1 - }) + [productId]: (state[productId] || 0) + 1, + }); + } default: - return state + return state; } } export default function cart(state = initialState, action) { switch (action.type) { case CHECKOUT_REQUEST: - return initialState + return initialState; case CHECKOUT_FAILURE: - return action.cart + return action.cart; default: return { addedIds: addedIds(state.addedIds, action), - quantityById: quantityById(state.quantityById, action) - } + quantityById: quantityById(state.quantityById, action), + }; } } export function getQuantity(state, productId) { - return state.quantityById[productId] || 0 + return state.quantityById[productId] || 0; } export function getAddedIds(state) { - return state.addedIds + return state.addedIds; } diff --git a/examples/shopping-cart/reducers/index.js b/examples/shopping-cart/reducers/index.js index 0490144712..18db23ac62 100644 --- a/examples/shopping-cart/reducers/index.js +++ b/examples/shopping-cart/reducers/index.js @@ -1,12 +1,12 @@ -import { combineReducers } from 'redux' -import { default as cart, getQuantity, getAddedIds } from './cart' -import { default as products, getProduct } from './products' +import { combineReducers } from 'redux'; +import { default as cart, getQuantity, getAddedIds } from './cart'; +import { default as products, getProduct } from './products'; export function getTotal(state) { return getAddedIds(state.cart).reduce((total, id) => total + getProduct(state.products, id).price * getQuantity(state.cart, id), 0 - ).toFixed(2) + ).toFixed(2); } export function getCartProducts(state) { @@ -14,12 +14,12 @@ export function getCartProducts(state) { {}, getProduct(state.products, id), { - quantity: getQuantity(state.cart, id) + quantity: getQuantity(state.cart, id), } - )) + )); } export default combineReducers({ cart, - products -}) + products, +}); diff --git a/examples/shopping-cart/reducers/products.js b/examples/shopping-cart/reducers/products.js index eee1a0200b..dec38d9cbc 100644 --- a/examples/shopping-cart/reducers/products.js +++ b/examples/shopping-cart/reducers/products.js @@ -1,14 +1,14 @@ -import { combineReducers } from 'redux' -import { RECEIVE_PRODUCTS, ADD_TO_CART } from '../constants/ActionTypes' +import { combineReducers } from 'redux'; +import { RECEIVE_PRODUCTS, ADD_TO_CART } from '../constants/ActionTypes'; function products(state, action) { switch (action.type) { case ADD_TO_CART: return Object.assign({}, state, { - inventory: state.inventory - 1 - }) + inventory: state.inventory - 1, + }); default: - return state + return state; } } @@ -18,39 +18,41 @@ function byId(state = {}, action) { return Object.assign({}, state, action.products.reduce((obj, product) => { - obj[product.id] = product - return obj + const newObj = obj; + newObj[product.id] = product; + return newObj; }, {}) - ) - default: - const { productId } = action + ); + default: { + const { productId } = action; if (productId) { return Object.assign({}, state, { - [productId]: products(state[productId], action) - }) + [productId]: products(state[productId], action), + }); } - return state + return state; + } } } function visibleIds(state = [], action) { switch (action.type) { case RECEIVE_PRODUCTS: - return action.products.map(product => product.id) + return action.products.map(product => product.id); default: - return state + return state; } } export default combineReducers({ byId, - visibleIds -}) + visibleIds, +}); export function getProduct(state, id) { - return state.byId[id] + return state.byId[id]; } export function getVisibleProducts(state) { - return state.visibleIds.map(id => getProduct(state, id)) + return state.visibleIds.map(id => getProduct(state, id)); } diff --git a/examples/shopping-cart/test/components/Cart.spec.js b/examples/shopping-cart/test/components/Cart.spec.js index d47e69c22e..cec646b4ae 100644 --- a/examples/shopping-cart/test/components/Cart.spec.js +++ b/examples/shopping-cart/test/components/Cart.spec.js @@ -1,43 +1,43 @@ -import expect from 'expect' -import React from 'react' -import { shallow } from 'enzyme' -import Cart from '../../components/Cart' -import Product from '../../components/Product' +import expect from 'expect'; +import React from 'react'; +import { shallow } from 'enzyme'; +import Cart from '../../components/Cart'; +import Product from '../../components/Product'; function setup(total, products = []) { const actions = { - onCheckoutClicked: expect.createSpy() - } + onCheckoutClicked: expect.createSpy(), + }; const component = shallow( - ) + ); return { - component: component, - actions: actions, + component, + actions, button: component.find('button'), products: component.find(Product), em: component.find('em'), - p: component.find('p') - } + p: component.find('p'), + }; } describe('Cart component', () => { it('should display total', () => { - const { p } = setup('76') - expect(p.text()).toMatch(/^Total: \$76/) - }) + const { p } = setup('76'); + expect(p.text()).toMatch(/^Total: \$76/); + }); it('should display add some products message', () => { - const { em } = setup() - expect(em.text()).toMatch(/^Please add some products to cart/) - }) + const { em } = setup(); + expect(em.text()).toMatch(/^Please add some products to cart/); + }); it('should disable button', () => { - const { button } = setup() - expect(button.prop('disabled')).toEqual('disabled') - }) + const { button } = setup(); + expect(button.prop('disabled')).toEqual('disabled'); + }); describe('when given product', () => { const product = [ @@ -45,30 +45,30 @@ describe('Cart component', () => { id: 1, title: 'Product 1', price: 9.99, - quantity: 1 - } - ] + quantity: 1, + }, + ]; it('should render products', () => { - const { products } = setup('9.99', product) + const { products } = setup('9.99', product); const props = { title: product[0].title, price: product[0].price, - quantity: product[0].quantity - } + quantity: product[0].quantity, + }; - expect(products.at(0).props()).toEqual(props) - }) + expect(products.at(0).props()).toEqual(props); + }); it('should not disable button', () => { - const { button } = setup('9.99', product) - expect(button.prop('disabled')).toEqual('') - }) + const { button } = setup('9.99', product); + expect(button.prop('disabled')).toEqual(''); + }); it('should call action on button click', () => { - const { button, actions } = setup('9.99', product) - button.simulate('click') - expect(actions.onCheckoutClicked).toHaveBeenCalled() - }) - }) -}) + const { button, actions } = setup('9.99', product); + button.simulate('click'); + expect(actions.onCheckoutClicked).toHaveBeenCalled(); + }); + }); +}); diff --git a/examples/shopping-cart/test/components/Product.spec.js b/examples/shopping-cart/test/components/Product.spec.js index a8d0deab7a..ed584772d1 100644 --- a/examples/shopping-cart/test/components/Product.spec.js +++ b/examples/shopping-cart/test/components/Product.spec.js @@ -1,28 +1,28 @@ -import expect from 'expect' -import React from 'react' -import { shallow } from 'enzyme' -import Product from '../../components/Product' +import expect from 'expect'; +import React from 'react'; +import { shallow } from 'enzyme'; +import Product from '../../components/Product'; function setup(props) { const component = shallow( - ) + ); return { - component: component - } + component, + }; } describe('Product component', () => { it('should render title and price', () => { - const { component } = setup({ title: 'Test Product', price: 9.99 }) - expect(component.text()).toMatch(/^ Test Product - \$9.99 {2}$/) - }) + const { component } = setup({ title: 'Test Product', price: 9.99 }); + expect(component.text()).toMatch(/^ Test Product - \$9.99 {2}$/); + }); describe('when given quantity', () => { it('should render title, price, and quantity', () => { - const { component } = setup({ title: 'Test Product', price: 9.99, quantity: 6 }) - expect(component.text()).toMatch(/^ Test Product - \$9.99 x 6 $/) - }) - }) -}) + const { component } = setup({ title: 'Test Product', price: 9.99, quantity: 6 }); + expect(component.text()).toMatch(/^ Test Product - \$9.99 x 6 $/); + }); + }); +}); diff --git a/examples/shopping-cart/test/components/ProductItem.spec.js b/examples/shopping-cart/test/components/ProductItem.spec.js index 1d960a2e19..59d9cdfbb1 100644 --- a/examples/shopping-cart/test/components/ProductItem.spec.js +++ b/examples/shopping-cart/test/components/ProductItem.spec.js @@ -1,71 +1,71 @@ -import expect from 'expect' -import React from 'react' -import { shallow } from 'enzyme' -import Product from '../../components/Product' -import ProductItem from '../../components/ProductItem' +import expect from 'expect'; +import React from 'react'; +import { shallow } from 'enzyme'; +import Product from '../../components/Product'; +import ProductItem from '../../components/ProductItem'; function setup(product) { const actions = { - onAddToCartClicked: expect.createSpy() - } + onAddToCartClicked: expect.createSpy(), + }; const component = shallow( - ) + ); return { - component: component, - actions: actions, + component, + actions, button: component.find('button'), - product: component.find(Product) - } + product: component.find(Product), + }; } -let productProps +let productProps; describe('ProductItem component', () => { beforeEach(() => { productProps = { title: 'Product 1', price: 9.99, - inventory: 6 - } - }) + inventory: 6, + }; + }); it('should render product', () => { - const { product } = setup(productProps) - expect(product.props()).toEqual({ title: 'Product 1', price: 9.99 }) - }) + const { product } = setup(productProps); + expect(product.props()).toEqual({ title: 'Product 1', price: 9.99 }); + }); it('should render Add To Cart message', () => { - const { button } = setup(productProps) - expect(button.text()).toMatch(/^Add to cart/) - }) + const { button } = setup(productProps); + expect(button.text()).toMatch(/^Add to cart/); + }); it('should not disable button', () => { - const { button } = setup(productProps) - expect(button.prop('disabled')).toEqual('') - }) + const { button } = setup(productProps); + expect(button.prop('disabled')).toEqual(''); + }); it('should call action on button click', () => { - const { button, actions } = setup(productProps) - button.simulate('click') - expect(actions.onAddToCartClicked).toHaveBeenCalled() - }) + const { button, actions } = setup(productProps); + button.simulate('click'); + expect(actions.onAddToCartClicked).toHaveBeenCalled(); + }); describe('when product inventory is 0', () => { beforeEach(() => { - productProps.inventory = 0 - }) + productProps.inventory = 0; + }); it('should render Sold Out message', () => { - const { button } = setup(productProps) - expect(button.text()).toMatch(/^Sold Out/) - }) + const { button } = setup(productProps); + expect(button.text()).toMatch(/^Sold Out/); + }); it('should disable button', () => { - const { button } = setup(productProps) - expect(button.prop('disabled')).toEqual('disabled') - }) - }) -}) + const { button } = setup(productProps); + expect(button.prop('disabled')).toEqual('disabled'); + }); + }); +}); diff --git a/examples/shopping-cart/test/components/ProductsList.spec.js b/examples/shopping-cart/test/components/ProductsList.spec.js index 8c34274dfc..f16400b9be 100644 --- a/examples/shopping-cart/test/components/ProductsList.spec.js +++ b/examples/shopping-cart/test/components/ProductsList.spec.js @@ -1,28 +1,28 @@ -import expect from 'expect' -import React from 'react' -import { shallow } from 'enzyme' -import ProductsList from '../../components/ProductsList' +import expect from 'expect'; +import React from 'react'; +import { shallow } from 'enzyme'; +import ProductsList from '../../components/ProductsList'; function setup(props) { const component = shallow( {props.children} - ) + ); return { - component: component, + component, children: component.children().at(1), - h3: component.find('h3') - } + h3: component.find('h3'), + }; } describe('ProductsList component', () => { it('should render title', () => { - const { h3 } = setup({ title: 'Test Products' }) - expect(h3.text()).toMatch(/^Test Products$/) - }) + const { h3 } = setup({ title: 'Test Products' }); + expect(h3.text()).toMatch(/^Test Products$/); + }); it('should render children', () => { - const { children } = setup({ title: 'Test Products', children: 'Test Children' }) - expect(children.text()).toMatch(/^Test Children$/) - }) -}) + const { children } = setup({ title: 'Test Products', children: 'Test Children' }); + expect(children.text()).toMatch(/^Test Children$/); + }); +}); diff --git a/examples/shopping-cart/test/reducers/cart.spec.js b/examples/shopping-cart/test/reducers/cart.spec.js index 353e58b832..6522cbbd46 100644 --- a/examples/shopping-cart/test/reducers/cart.spec.js +++ b/examples/shopping-cart/test/reducers/cart.spec.js @@ -1,44 +1,44 @@ -import expect from 'expect' -import cart from '../../reducers/cart' +import expect from 'expect'; +import cart from '../../reducers/cart'; describe('reducers', () => { describe('cart', () => { const initialState = { addedIds: [], - quantityById: {} - } + quantityById: {}, + }; it('should provide the initial state', () => { - expect(cart(undefined, {})).toEqual(initialState) - }) + expect(cart(undefined, {})).toEqual(initialState); + }); it('should handle CHECKOUT_REQUEST action', () => { - expect(cart({}, { type: 'CHECKOUT_REQUEST' })).toEqual(initialState) - }) + expect(cart({}, { type: 'CHECKOUT_REQUEST' })).toEqual(initialState); + }); it('should handle CHECKOUT_FAILURE action', () => { - expect(cart({}, { type: 'CHECKOUT_FAILURE', cart: 'cart state' })).toEqual('cart state') - }) + expect(cart({}, { type: 'CHECKOUT_FAILURE', cart: 'cart state' })).toEqual('cart state'); + }); it('should handle ADD_TO_CART action', () => { expect(cart(initialState, { type: 'ADD_TO_CART', productId: 1 })).toEqual({ - addedIds: [ 1 ], - quantityById: { 1: 1 } - }) - }) + addedIds: [1], + quantityById: { 1: 1 }, + }); + }); describe('when product is already in cart', () => { it('should handle ADD_TO_CART action', () => { const state = { - addedIds: [ 1, 2 ], - quantityById: { 1: 1, 2: 1 } - } + addedIds: [1, 2], + quantityById: { 1: 1, 2: 1 }, + }; expect(cart(state, { type: 'ADD_TO_CART', productId: 2 })).toEqual({ - addedIds: [ 1, 2 ], - quantityById: { 1: 1, 2: 2 } - }) - }) - }) - }) -}) + addedIds: [1, 2], + quantityById: { 1: 1, 2: 2 }, + }); + }); + }); + }); +}); diff --git a/examples/shopping-cart/test/reducers/products.spec.js b/examples/shopping-cart/test/reducers/products.spec.js index c5c8a6f016..e1f5b34651 100644 --- a/examples/shopping-cart/test/reducers/products.spec.js +++ b/examples/shopping-cart/test/reducers/products.spec.js @@ -1,5 +1,5 @@ -import expect from 'expect' -import products from '../../reducers/products' +import expect from 'expect'; +import products from '../../reducers/products'; describe('reducers', () => { describe('products', () => { @@ -9,29 +9,29 @@ describe('reducers', () => { products: [ { id: 1, - title: 'Product 1' + title: 'Product 1', }, { id: 2, - title: 'Product 2' - } - ] - } + title: 'Product 2', + }, + ], + }; expect(products({}, action)).toEqual({ byId: { 1: { id: 1, - title: 'Product 1' + title: 'Product 1', }, 2: { id: 2, - title: 'Product 2' - } + title: 'Product 2', + }, }, - visibleIds: [ 1, 2 ] - }) - }) + visibleIds: [1, 2], + }); + }); it('should handle ADD_TO_CART action', () => { const state = { @@ -39,21 +39,21 @@ describe('reducers', () => { 1: { id: 1, title: 'Product 1', - inventory: 1 - } - } - } + inventory: 1, + }, + }, + }; expect(products(state, { type: 'ADD_TO_CART', productId: 1 })).toEqual({ byId: { 1: { id: 1, title: 'Product 1', - inventory: 0 - } + inventory: 0, + }, }, - visibleIds: [] - }) - }) - }) -}) + visibleIds: [], + }); + }); + }); +}); diff --git a/examples/shopping-cart/test/reducers/selectors.spec.js b/examples/shopping-cart/test/reducers/selectors.spec.js index e07d4e0b56..66b5f85ffd 100644 --- a/examples/shopping-cart/test/reducers/selectors.spec.js +++ b/examples/shopping-cart/test/reducers/selectors.spec.js @@ -1,85 +1,85 @@ -import expect from 'expect' -import { getTotal, getCartProducts } from '../../reducers' +import expect from 'expect'; +import { getTotal, getCartProducts } from '../../reducers'; describe('selectors', () => { describe('getTotal', () => { it('should return price total', () => { const state = { cart: { - addedIds: [ 1, 2, 3 ], + addedIds: [1, 2, 3], quantityById: { 1: 4, 2: 2, - 3: 1 - } + 3: 1, + }, }, products: { byId: { 1: { id: 1, - price: 1.99 + price: 1.99, }, 2: { id: 1, - price: 4.99 + price: 4.99, }, 3: { id: 1, - price: 9.99 - } - } - } - } - expect(getTotal(state)).toBe('27.93') - }) - }) + price: 9.99, + }, + }, + }, + }; + expect(getTotal(state)).toBe('27.93'); + }); + }); describe('getCartProducts', () => { it('should return products with quantity', () => { const state = { cart: { - addedIds: [ 1, 2, 3 ], + addedIds: [1, 2, 3], quantityById: { 1: 4, 2: 2, - 3: 1 - } + 3: 1, + }, }, products: { byId: { 1: { id: 1, - price: 1.99 + price: 1.99, }, 2: { id: 1, - price: 4.99 + price: 4.99, }, 3: { id: 1, - price: 9.99 - } - } - } - } + price: 9.99, + }, + }, + }, + }; expect(getCartProducts(state)).toEqual([ { id: 1, price: 1.99, - quantity: 4 + quantity: 4, }, { id: 1, price: 4.99, - quantity: 2 + quantity: 2, }, { id: 1, price: 9.99, - quantity: 1 - } - ]) - }) - }) -}) + quantity: 1, + }, + ]); + }); + }); +}); diff --git a/examples/testAll.js b/examples/testAll.js index 2667e71ea1..fe696865bb 100644 --- a/examples/testAll.js +++ b/examples/testAll.js @@ -2,36 +2,36 @@ * Runs an ordered set of commands within each of the build directories. */ -import fs from 'fs' -import path from 'path' -import { spawnSync } from 'child_process' +import fs from 'fs'; +import path from 'path'; +import { spawnSync } from 'child_process'; -var exampleDirs = fs.readdirSync(__dirname).filter((file) => { - return fs.statSync(path.join(__dirname, file)).isDirectory() -}) +const exampleDirs = fs.readdirSync(__dirname).filter((file) => ( + fs.statSync(path.join(__dirname, file)).isDirectory() +)); // Ordering is important here. `npm install` must come first. -var cmdArgs = [ - { cmd: 'npm', args: [ 'install' ] }, - { cmd: 'npm', args: [ 'test' ] } -] +const cmdArgs = [ + { cmd: 'npm', args: ['install'] }, + { cmd: 'npm', args: ['test'] }, +]; for (const dir of exampleDirs) { for (const cmdArg of cmdArgs) { // declare opts in this scope to avoid https://github.com/joyent/node/issues/9158 const opts = { cwd: path.join(__dirname, dir), - stdio: 'inherit' - } + stdio: 'inherit', + }; - let result = {} + let result = {}; if (process.platform === 'win32') { - result = spawnSync(cmdArg.cmd + '.cmd', cmdArg.args, opts) + result = spawnSync(`${cmdArg.cmd}.cmd`, cmdArg.args, opts); } else { - result = spawnSync(cmdArg.cmd, cmdArg.args, opts) + result = spawnSync(cmdArg.cmd, cmdArg.args, opts); } if (result.status !== 0) { - throw new Error('Building examples exited with non-zero') + throw new Error('Building examples exited with non-zero'); } } } diff --git a/examples/todomvc/actions/index.js b/examples/todomvc/actions/index.js index 2e52a7cf1b..6424722129 100644 --- a/examples/todomvc/actions/index.js +++ b/examples/todomvc/actions/index.js @@ -1,25 +1,25 @@ -import * as types from '../constants/ActionTypes' +import * as types from '../constants/ActionTypes'; export function addTodo(text) { - return { type: types.ADD_TODO, text } + return { type: types.ADD_TODO, text }; } export function deleteTodo(id) { - return { type: types.DELETE_TODO, id } + return { type: types.DELETE_TODO, id }; } export function editTodo(id, text) { - return { type: types.EDIT_TODO, id, text } + return { type: types.EDIT_TODO, id, text }; } export function completeTodo(id) { - return { type: types.COMPLETE_TODO, id } + return { type: types.COMPLETE_TODO, id }; } export function completeAll() { - return { type: types.COMPLETE_ALL } + return { type: types.COMPLETE_ALL }; } export function clearCompleted() { - return { type: types.CLEAR_COMPLETED } + return { type: types.CLEAR_COMPLETED }; } diff --git a/examples/todomvc/components/Footer.js b/examples/todomvc/components/Footer.js index 8ae9c59983..c18aae0e50 100644 --- a/examples/todomvc/components/Footer.js +++ b/examples/todomvc/components/Footer.js @@ -1,48 +1,50 @@ -import React, { PropTypes, Component } from 'react' -import classnames from 'classnames' -import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' +import React, { PropTypes, Component } from 'react'; +import classnames from 'classnames'; +import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'; const FILTER_TITLES = { [SHOW_ALL]: 'All', [SHOW_ACTIVE]: 'Active', - [SHOW_COMPLETED]: 'Completed' -} + [SHOW_COMPLETED]: 'Completed', +}; class Footer extends Component { renderTodoCount() { - const { activeCount } = this.props - const itemWord = activeCount === 1 ? 'item' : 'items' + const { activeCount } = this.props; + const itemWord = activeCount === 1 ? 'item' : 'items'; return ( {activeCount || 'No'} {itemWord} left - ) + ); } renderFilterLink(filter) { - const title = FILTER_TITLES[filter] - const { filter: selectedFilter, onShow } = this.props + const title = FILTER_TITLES[filter]; + const { filter: selectedFilter, onShow } = this.props; return ( onShow(filter)}> + style={{ cursor: 'pointer' }} + onClick={() => onShow(filter)} + > {title} - ) + ); } renderClearButton() { - const { completedCount, onClearCompleted } = this.props + const { completedCount, onClearCompleted } = this.props; if (completedCount > 0) { return ( - - ) + ); } + + return null; } render() { @@ -50,7 +52,7 @@ class Footer extends Component {
{this.renderTodoCount()}
    - {[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter => + {[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map(filter =>
  • {this.renderFilterLink(filter)}
  • @@ -58,7 +60,7 @@ class Footer extends Component {
{this.renderClearButton()}
- ) + ); } } @@ -67,7 +69,7 @@ Footer.propTypes = { activeCount: PropTypes.number.isRequired, filter: PropTypes.string.isRequired, onClearCompleted: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired -} + onShow: PropTypes.func.isRequired, +}; -export default Footer +export default Footer; diff --git a/examples/todomvc/components/Header.js b/examples/todomvc/components/Header.js index aeb8303819..d4aeb99ef6 100644 --- a/examples/todomvc/components/Header.js +++ b/examples/todomvc/components/Header.js @@ -1,10 +1,15 @@ -import React, { PropTypes, Component } from 'react' -import TodoTextInput from './TodoTextInput' +import React, { PropTypes, Component } from 'react'; +import TodoTextInput from './TodoTextInput'; class Header extends Component { + constructor() { + super(); + this.handleSave = this.handleSave.bind(this); + } + handleSave(text) { if (text.length !== 0) { - this.props.addTodo(text) + this.props.addTodo(text); } } @@ -13,15 +18,16 @@ class Header extends Component {

todos

+ onSave={this.handleSave} + placeholder="What needs to be done?" + />
- ) + ); } } Header.propTypes = { - addTodo: PropTypes.func.isRequired -} + addTodo: PropTypes.func.isRequired, +}; -export default Header +export default Header; diff --git a/examples/todomvc/components/MainSection.js b/examples/todomvc/components/MainSection.js index f5c2804010..c649fddcf3 100644 --- a/examples/todomvc/components/MainSection.js +++ b/examples/todomvc/components/MainSection.js @@ -1,65 +1,72 @@ -import React, { Component, PropTypes } from 'react' -import TodoItem from './TodoItem' -import Footer from './Footer' -import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' +import React, { Component, PropTypes } from 'react'; +import TodoItem from './TodoItem'; +import Footer from './Footer'; +import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'; const TODO_FILTERS = { [SHOW_ALL]: () => true, [SHOW_ACTIVE]: todo => !todo.completed, - [SHOW_COMPLETED]: todo => todo.completed -} + [SHOW_COMPLETED]: todo => todo.completed, +}; class MainSection extends Component { constructor(props, context) { - super(props, context) - this.state = { filter: SHOW_ALL } + super(props, context); + this.state = { filter: SHOW_ALL }; + this.handleClearCompleted = this.handleClearCompleted.bind(this); + this.handleShow = this.handleShow.bind(this); } handleClearCompleted() { - this.props.actions.clearCompleted() + this.props.actions.clearCompleted(); } handleShow(filter) { - this.setState({ filter }) + this.setState({ filter }); } renderToggleAll(completedCount) { - const { todos, actions } = this.props + const { todos, actions } = this.props; if (todos.length > 0) { return ( - ) + type="checkbox" + checked={completedCount === todos.length} + onChange={actions.completeAll} + /> + ); } + + return null; } renderFooter(completedCount) { - const { todos } = this.props - const { filter } = this.state - const activeCount = todos.length - completedCount + const { todos } = this.props; + const { filter } = this.state; + const activeCount = todos.length - completedCount; if (todos.length) { return (