diff --git a/examples/with-redux-reselect-recompose/.babelrc b/examples/with-redux-reselect-recompose/.babelrc new file mode 100644 index 00000000..170348f7 --- /dev/null +++ b/examples/with-redux-reselect-recompose/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + "next/babel" + ], + "plugins": [ + ["module-resolver", { + "root": ["./"] + }] + ] +} \ No newline at end of file diff --git a/examples/with-redux-reselect-recompose/.eslintrc b/examples/with-redux-reselect-recompose/.eslintrc new file mode 100644 index 00000000..7fccbb8f --- /dev/null +++ b/examples/with-redux-reselect-recompose/.eslintrc @@ -0,0 +1,46 @@ +{ + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "env": { + "browser": true, + "amd": true, + "es6": true, + "node": true, + "mocha": true + }, + "extends": [ + "airbnb", + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings" + ], + "rules": { + "quotes": [ 1, "single" ], + "no-undef": 1, + "no-extra-semi": 1, + "no-console": 1, + "no-unused-vars": 1, + "no-trailing-spaces": [1, { "skipBlankLines": true }], + "no-unreachable": 1, + "react/jsx-uses-react": 1, + "react/jsx-uses-vars": 1, + "react/jsx-filename-extension": 0, + "react/prop-types": 0, // Disabled by this issue https://github.com/acdlite/recompose/issues/150 + "import/no-unresolved": [0, {"commonjs": true, "amd": true}], + "import/named": 1, + "import/namespace": 1, + "import/default": 1, + "import/export": 1, + "import/no-extraneous-dependencies": 0, + "import/extensions": 0 + } +} \ No newline at end of file diff --git a/examples/with-redux-reselect-recompose/.gitignore b/examples/with-redux-reselect-recompose/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/examples/with-redux-reselect-recompose/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/examples/with-redux-reselect-recompose/README.md b/examples/with-redux-reselect-recompose/README.md new file mode 100644 index 00000000..ae0771b6 --- /dev/null +++ b/examples/with-redux-reselect-recompose/README.md @@ -0,0 +1,36 @@ +[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-redux) + +# Redux with reselect and recompose example + +## How to use + +Download the example [or clone the repo](https://github.com/zeit/next.js): + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-redux-reselect-recompose +cd with-redux-reselect-recompose +``` + +Install it and run: + +```bash +npm install +npm run dev +``` + +OR + +```bash +yarn +yarn dev +``` + + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + +## The idea behind the example +This example is based on the great work of [with-redux](https://github.com/zeit/next.js/tree/v3-beta/examples/with-redux) example with the addition of [reselect](https://github.com/reactjs/reselect) and [recompose](https://github.com/acdlite/recompose) diff --git a/examples/with-redux-reselect-recompose/actions/index.js b/examples/with-redux-reselect-recompose/actions/index.js new file mode 100644 index 00000000..edb640ce --- /dev/null +++ b/examples/with-redux-reselect-recompose/actions/index.js @@ -0,0 +1,9 @@ +import { TICK, ADD } from 'constants/actionTypes' + +export const addCount = () => ({ type: ADD }) + +export const setClock = (light, ts) => ({ type: TICK, light, ts }) + +export const serverRenderClock = isServer => dispatch => dispatch(setClock(!isServer, Date.now())) + +export const startClock = () => dispatch => setInterval(() => dispatch(setClock(true, Date.now())), 800) diff --git a/examples/with-redux-reselect-recompose/components/addCount.js b/examples/with-redux-reselect-recompose/components/addCount.js new file mode 100644 index 00000000..42224c41 --- /dev/null +++ b/examples/with-redux-reselect-recompose/components/addCount.js @@ -0,0 +1,23 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { compose, setDisplayName, pure, setPropTypes } from 'recompose' + +const AddCount = ({ count, addCount }) => +
+ +

AddCount: {count}

+ +
+ +export default compose( + setDisplayName('AddCount'), + setPropTypes({ + count: PropTypes.number, + addCount: PropTypes.func + }), + pure +)(AddCount) diff --git a/examples/with-redux-reselect-recompose/components/clock.js b/examples/with-redux-reselect-recompose/components/clock.js new file mode 100644 index 00000000..59fbfd81 --- /dev/null +++ b/examples/with-redux-reselect-recompose/components/clock.js @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { compose, pure, setDisplayName, setPropTypes } from 'recompose' + +const format = t => `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}` + +const pad = n => n < 10 ? `0${n}` : n + +const Clock = ({ lastUpdate, light }) => +
+ {format(new Date(lastUpdate))} + +
+ +export default compose( + setDisplayName('Clock'), + setPropTypes({ + lastUpdate: PropTypes.number, + light: PropTypes.bool + }), + pure +)(Clock) diff --git a/examples/with-redux-reselect-recompose/components/page.js b/examples/with-redux-reselect-recompose/components/page.js new file mode 100644 index 00000000..629727c3 --- /dev/null +++ b/examples/with-redux-reselect-recompose/components/page.js @@ -0,0 +1,29 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Link from 'next/link' +import { compose, setDisplayName, pure, setPropTypes } from 'recompose' +import Clock from './clock' +import AddCount from './addCount' + +const Page = ({ title, linkTo, light, lastUpdate, count, addCount }) => +
+

{title}

+ + + +
+ +export default compose( + setDisplayName('Page'), + setPropTypes({ + title: PropTypes.string, + linkTo: PropTypes.string, + light: PropTypes.bool, + lastUpdate: PropTypes.number, + count: PropTypes.number, + addCount: PropTypes.func + }), + pure +)(Page) diff --git a/examples/with-redux-reselect-recompose/constants/actionTypes.js b/examples/with-redux-reselect-recompose/constants/actionTypes.js new file mode 100644 index 00000000..bd6338c5 --- /dev/null +++ b/examples/with-redux-reselect-recompose/constants/actionTypes.js @@ -0,0 +1,2 @@ +export const ADD = 'ADD' +export const TICK = 'TICK' diff --git a/examples/with-redux-reselect-recompose/containers/page.js b/examples/with-redux-reselect-recompose/containers/page.js new file mode 100644 index 00000000..1cf8d96f --- /dev/null +++ b/examples/with-redux-reselect-recompose/containers/page.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux' +import { addCount } from 'actions' +import { selectLight, selectLastUpdate, selectCount } from 'selectors' +import { createSelector } from 'reselect' +import { compose, setDisplayName, pure } from 'recompose' +import Page from 'components/page' + +export default compose( + setDisplayName('PageContainer'), + connect(createSelector( + selectLight(), + selectLastUpdate(), + selectCount(), + (light, lastUpdate, count) => ({ light, lastUpdate, count }) + ), { addCount }), + pure +)(Page) diff --git a/examples/with-redux-reselect-recompose/package.json b/examples/with-redux-reselect-recompose/package.json new file mode 100644 index 00000000..0c36801b --- /dev/null +++ b/examples/with-redux-reselect-recompose/package.json @@ -0,0 +1,35 @@ +{ + "name": "with-redux-reselect-recompose", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start", + "lint": "eslint --ext .js,.js ." + }, + "dependencies": { + "babel-plugin-module-resolver": "^2.7.1", + "next": "latest", + "next-redux-wrapper": "^1.0.0", + "prop-types": "^15.5.10", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-redux": "^5.0.1", + "recompose": "^0.23.5", + "redux": "^3.6.0", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.2.0", + "reselect": "^3.0.1" + }, + "author": "Phuc Nguyen Hoang", + "license": "MIT", + "devDependencies": { + "babel-eslint": "7.1.1", + "eslint": "3.11.1", + "eslint-config-airbnb": "13.0.0", + "eslint-loader": "^1.9.0", + "eslint-plugin-import": "2.2.0", + "eslint-plugin-jsx-a11y": "2.2.3", + "eslint-plugin-react": "6.7.1" + } +} diff --git a/examples/with-redux-reselect-recompose/pages/index.js b/examples/with-redux-reselect-recompose/pages/index.js new file mode 100644 index 00000000..e37b1ca5 --- /dev/null +++ b/examples/with-redux-reselect-recompose/pages/index.js @@ -0,0 +1,30 @@ +import { startClock, addCount, serverRenderClock } from 'actions' +import Page from 'containers/page' +import withRedux from 'next-redux-wrapper' +import { compose, setDisplayName, pure, lifecycle, withProps } from 'recompose' +import initStore from '../store' + +const Counter = compose( + setDisplayName('IndexPage'), + withProps({ + title: 'Index page', + linkTo: '/other' + }), + lifecycle({ + componentDidMount () { + this.timer = this.props.startClock() + }, + componentWillUnmount () { + clearInterval(this.timer) + } + }), + pure +)(Page) + +Counter.getInitialProps = ({ store, isServer }) => { + store.dispatch(serverRenderClock(isServer)) + store.dispatch(addCount()) + return { isServer } +} + +export default withRedux(initStore, null, { startClock })(Counter) diff --git a/examples/with-redux-reselect-recompose/pages/other.js b/examples/with-redux-reselect-recompose/pages/other.js new file mode 100644 index 00000000..4e6cd767 --- /dev/null +++ b/examples/with-redux-reselect-recompose/pages/other.js @@ -0,0 +1,30 @@ +import { startClock, addCount, serverRenderClock } from 'actions' +import Page from 'containers/page' +import withRedux from 'next-redux-wrapper' +import { compose, setDisplayName, pure, lifecycle, withProps } from 'recompose' +import initStore from '../store' + +const Counter = compose( + setDisplayName('OtherPage'), + withProps({ + title: 'Other page', + linkTo: '/' + }), + lifecycle({ + componentDidMount () { + this.timer = this.props.startClock() + }, + componentWillUnmount () { + clearInterval(this.timer) + } + }), + pure +)(Page) + +Counter.getInitialProps = ({ store, isServer }) => { + store.dispatch(serverRenderClock(isServer)) + store.dispatch(addCount()) + return { isServer } +} + +export default withRedux(initStore, null, { startClock })(Counter) diff --git a/examples/with-redux-reselect-recompose/reducers/count.js b/examples/with-redux-reselect-recompose/reducers/count.js new file mode 100644 index 00000000..3a678552 --- /dev/null +++ b/examples/with-redux-reselect-recompose/reducers/count.js @@ -0,0 +1,30 @@ +import { ADD, TICK } from 'constants/actionTypes' + +export const initialState = { + lastUpdate: 0, + light: false, + count: 0 +} + +export default (state = initialState, action) => { + const { type, ts, light } = action + + switch (type) { + case TICK: { + return Object.assign({}, state, { + lastUpdate: ts, + light: !!light + }) + } + + case ADD: { + return Object.assign({}, state, { + count: state.count + 1 + }) + } + + default: { + return state + } + } +} diff --git a/examples/with-redux-reselect-recompose/reducers/index.js b/examples/with-redux-reselect-recompose/reducers/index.js new file mode 100644 index 00000000..b97632c0 --- /dev/null +++ b/examples/with-redux-reselect-recompose/reducers/index.js @@ -0,0 +1,10 @@ +import { combineReducers } from 'redux' +import count, { initialState as countState } from './count' + +export const intitialState = { + count: countState +} + +export default combineReducers({ + count +}) diff --git a/examples/with-redux-reselect-recompose/selectors/index.js b/examples/with-redux-reselect-recompose/selectors/index.js new file mode 100644 index 00000000..4260bb27 --- /dev/null +++ b/examples/with-redux-reselect-recompose/selectors/index.js @@ -0,0 +1,18 @@ +import { createSelector } from 'reselect' + +export const selectState = () => state => state.count + +export const selectCount = () => createSelector( + selectState(), + count => count.count +) + +export const selectLight = () => createSelector( + selectState(), + count => count.light +) + +export const selectLastUpdate = () => createSelector( + selectState(), + count => count.lastUpdate +) diff --git a/examples/with-redux-reselect-recompose/store.js b/examples/with-redux-reselect-recompose/store.js new file mode 100644 index 00000000..7449d8ea --- /dev/null +++ b/examples/with-redux-reselect-recompose/store.js @@ -0,0 +1,13 @@ +import thunkMiddleware from 'redux-thunk' +import { createStore, applyMiddleware, compose } from 'redux' +import { createLogger } from 'redux-logger' +import reducer, { initialState } from 'reducers' + +export default (state = initialState) => { + const middlewares = [thunkMiddleware, createLogger()] + return createStore( + reducer, + state, + compose(applyMiddleware(...middlewares)) + ) +}