1
0
Fork 0
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:
Nguyen Hoang Phuc 2017-07-12 03:17:00 +09:00 committed by Tim Neutkens
parent bd24e74a98
commit 530b561039
17 changed files with 373 additions and 0 deletions

View file

@ -0,0 +1,10 @@
{
"presets": [
"next/babel"
],
"plugins": [
["module-resolver", {
"root": ["./"]
}]
]
}

View 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
}
}

View file

@ -0,0 +1 @@
node_modules

View 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)

View 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)

View file

@ -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)

View 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)

View 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)

View file

@ -0,0 +1,2 @@
export const ADD = 'ADD'
export const TICK = 'TICK'

View 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)

View 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"
}
}

View 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)

View 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)

View 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
}
}
}

View 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
})

View 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
)

View 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))
)
}