mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Add example for usage of redux + reselect + recompose (#2523)
* implemented example for using redux with reselect and recompose * removed unused package * fixed linting issue * fixed linting issue
This commit is contained in:
parent
bd24e74a98
commit
530b561039
10
examples/with-redux-reselect-recompose/.babelrc
Normal file
10
examples/with-redux-reselect-recompose/.babelrc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"next/babel"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["module-resolver", {
|
||||||
|
"root": ["./"]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
46
examples/with-redux-reselect-recompose/.eslintrc
Normal file
46
examples/with-redux-reselect-recompose/.eslintrc
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
1
examples/with-redux-reselect-recompose/.gitignore
vendored
Normal file
1
examples/with-redux-reselect-recompose/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
36
examples/with-redux-reselect-recompose/README.md
Normal file
36
examples/with-redux-reselect-recompose/README.md
Normal file
|
@ -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)
|
9
examples/with-redux-reselect-recompose/actions/index.js
Normal file
9
examples/with-redux-reselect-recompose/actions/index.js
Normal file
|
@ -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)
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { compose, setDisplayName, pure, setPropTypes } from 'recompose'
|
||||||
|
|
||||||
|
const AddCount = ({ count, addCount }) =>
|
||||||
|
<div>
|
||||||
|
<style jsx>{`
|
||||||
|
div {
|
||||||
|
padding: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<h1>AddCount: <span>{count}</span></h1>
|
||||||
|
<button onClick={addCount}>Add To Count</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
setDisplayName('AddCount'),
|
||||||
|
setPropTypes({
|
||||||
|
count: PropTypes.number,
|
||||||
|
addCount: PropTypes.func
|
||||||
|
}),
|
||||||
|
pure
|
||||||
|
)(AddCount)
|
34
examples/with-redux-reselect-recompose/components/clock.js
Normal file
34
examples/with-redux-reselect-recompose/components/clock.js
Normal file
|
@ -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 }) =>
|
||||||
|
<div className={light ? 'light' : ''}>
|
||||||
|
{format(new Date(lastUpdate))}
|
||||||
|
<style jsx>{`
|
||||||
|
div {
|
||||||
|
padding: 15px;
|
||||||
|
display: inline-block;
|
||||||
|
color: #82FA58;
|
||||||
|
font: 50px menlo, monaco, monospace;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
background-color: #999;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
setDisplayName('Clock'),
|
||||||
|
setPropTypes({
|
||||||
|
lastUpdate: PropTypes.number,
|
||||||
|
light: PropTypes.bool
|
||||||
|
}),
|
||||||
|
pure
|
||||||
|
)(Clock)
|
29
examples/with-redux-reselect-recompose/components/page.js
Normal file
29
examples/with-redux-reselect-recompose/components/page.js
Normal file
|
@ -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 }) =>
|
||||||
|
<div>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
<Clock lastUpdate={lastUpdate} light={light} />
|
||||||
|
<AddCount count={count} addCount={addCount} />
|
||||||
|
<nav>
|
||||||
|
<Link href={linkTo}><a>Navigate</a></Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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)
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const ADD = 'ADD'
|
||||||
|
export const TICK = 'TICK'
|
17
examples/with-redux-reselect-recompose/containers/page.js
Normal file
17
examples/with-redux-reselect-recompose/containers/page.js
Normal file
|
@ -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)
|
35
examples/with-redux-reselect-recompose/package.json
Normal file
35
examples/with-redux-reselect-recompose/package.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
30
examples/with-redux-reselect-recompose/pages/index.js
Normal file
30
examples/with-redux-reselect-recompose/pages/index.js
Normal file
|
@ -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)
|
30
examples/with-redux-reselect-recompose/pages/other.js
Normal file
30
examples/with-redux-reselect-recompose/pages/other.js
Normal file
|
@ -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)
|
30
examples/with-redux-reselect-recompose/reducers/count.js
Normal file
30
examples/with-redux-reselect-recompose/reducers/count.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
examples/with-redux-reselect-recompose/reducers/index.js
Normal file
10
examples/with-redux-reselect-recompose/reducers/index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { combineReducers } from 'redux'
|
||||||
|
import count, { initialState as countState } from './count'
|
||||||
|
|
||||||
|
export const intitialState = {
|
||||||
|
count: countState
|
||||||
|
}
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
count
|
||||||
|
})
|
18
examples/with-redux-reselect-recompose/selectors/index.js
Normal file
18
examples/with-redux-reselect-recompose/selectors/index.js
Normal file
|
@ -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
|
||||||
|
)
|
13
examples/with-redux-reselect-recompose/store.js
Normal file
13
examples/with-redux-reselect-recompose/store.js
Normal file
|
@ -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))
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue