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