diff --git a/.eslintrc.yml b/.eslintrc.yml
index 3baeae1..94aadd5 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -4,6 +4,8 @@ env:
extends:
- 'airbnb'
parser: 'babel-eslint'
+plugins:
+ - 'react-hooks'
rules:
react/jsx-filename-extension:
- error
@@ -24,3 +26,4 @@ rules:
- 'Link'
specialLink:
- 'to'
+ react-hooks/rules-of-hooks: 'error'
diff --git a/.travis.yml b/.travis.yml
index 15a95fd..55f2bef 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@ language: node_js
node_js:
- 8
- - 6
install:
- yarn
diff --git a/README.md b/README.md
index 8750142..6ae18a8 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ This boilerplate would help you build a react/redux/react-router isomorphic/univ
- manage your style in [CSS Modules](https://github.com/css-modules/css-modules) way.
- babel v7
- optimize bundle size by implementing [tree shaking](https://webpack.js.org/guides/tree-shaking/)
+- react hooks!(I hate OO components)
## Concept
### Getting Started
diff --git a/babel.config.js b/babel.config.js
index 5ebf461..a8b5118 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,5 +1,5 @@
module.exports = function babelConfig(api) {
- api.cache(false);
+ api.cache(true); // set to other value if we handle env variable here
return {
presets: [
'@babel/react',
diff --git a/package.json b/package.json
index 8d83ec5..4953230 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
"name": "react-isomorphic-boilerplate",
- "version": "0.7.1",
+ "version": "0.7.2",
"main": "src/server/index.js",
"license": "MIT",
"engines": {
- "node": "^6.14.0 || ^8.10.0 || >=9.10.0"
+ "node": "^8.10.0 || >=9.10.0"
},
"scripts": {
"start": "DEBUG=*,-nodemon*,-express*,-send,-babel*,-eslint*,-css-modules* NODE_ENV=hot npx babel-node --inspect src/server/hot/index.js",
@@ -23,6 +23,7 @@
"@babel/node": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-transform-runtime": "^7.1.0",
+ "@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.1.5",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
@@ -45,6 +46,7 @@
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.11.1",
+ "eslint-plugin-react-hooks": "^0.0.0",
"file-loader": "^2.0.0",
"ignore-styles": "^5.0.1",
"mini-css-extract-plugin": "^0.4.4",
@@ -84,11 +86,11 @@
"lodash": "^4.17.4",
"normalizr": "^3.2.4",
"prop-types": "^15.6.0",
- "react": "^16.4.1",
- "react-dom": "^16.4.1",
+ "react": "^16.7.0-alpha.2",
+ "react-dom": "^16.7.0-alpha.2",
"react-helmet": "^5.2.0",
- "react-hot-loader": "^4.3.12",
- "react-redux": "^5.0.6",
+ "react-hot-loader": "^4.5.1",
+ "react-redux": "^6.0.0-beta.2",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"redux": "^4.0.0",
@@ -124,6 +126,7 @@
],
"require": [
"@babel/register",
+ "@babel/polyfill",
"ignore-styles",
"css-modules-require-hook/preset",
"./src/enzymeSetup.js"
@@ -131,7 +134,8 @@
},
"nyc": {
"require": [
- "@babel/register"
+ "@babel/register",
+ "@babel/polyfill"
],
"sourceMap": false,
"instrument": false
diff --git a/src/actions/index.js b/src/actions/index.js
index 7ca8cc6..21a490b 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -11,9 +11,11 @@ export function accumulateCount() {
}
export function dummy() {
- return {
- type: 'DUMMY_ACTION',
- };
+ return async dispatch => new Promise(() => setTimeout(() => {
+ dispatch({
+ type: 'DUMMY_ACTION',
+ });
+ }, 1000));
}
export function updateMe(me) {
diff --git a/src/containers/Home/FormPostTotalCount.js b/src/containers/Home/FormPostTotalCount.js
new file mode 100644
index 0000000..bde78ee
--- /dev/null
+++ b/src/containers/Home/FormPostTotalCount.js
@@ -0,0 +1,30 @@
+import React/* , { useEffect } */ from 'react';
+import { useRedux } from '../../hooks/useRedux';
+import useDummy from '../../hooks/useDummy';
+
+import style from './style.scss'; // eslint-disable-line no-unused-vars
+
+export default function FormPostTotalCount() {
+ const [state] = useRedux();
+ const [dummyTimes, triggerDummy] = useDummy();
+
+ return (
+
+
+ this component uses react hook
+
+
+ Total
+ {state.pages.home.posts.length}
+ Posts
+
+
+ {dummyTimes}
+ times dummy
+
+
+
+ );
+}
diff --git a/src/containers/Home/index.js b/src/containers/Home/index.js
index 714b57b..b3a59af 100644
--- a/src/containers/Home/index.js
+++ b/src/containers/Home/index.js
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { get as _get } from 'lodash';
import { dummy as dummyAct, fetchPosts as fetchPostsAction } from '../../actions';
import FormPostComponent from './FormPost';
+import FormPostTotalCount from './FormPostTotalCount';
import PostlistComponent from './Postlist';
import stdout from '../../stdout';
import style from './style.scss'; // eslint-disable-line no-unused-vars
@@ -45,6 +46,7 @@ export class Home extends React.Component {
+
{posts.map(p => )}
diff --git a/src/containers/Home/index.spec.js b/src/containers/Home/index.spec.js
index af5295f..864c86c 100644
--- a/src/containers/Home/index.spec.js
+++ b/src/containers/Home/index.spec.js
@@ -62,9 +62,9 @@ test('mapStateToProps', (t) => {
t.deepEqual(mappedProps.posts, [mockPost['1']]);
});
-test('mapDispatchToProps', (t) => {
+test('mapDispatchToProps', async (t) => {
const dispatchSpy = sinon.spy();
const dispatchers = mapDispatchToProps(dispatchSpy);
- dispatchers.dummyAction();
+ await dispatchers.dummyAction();
t.true(dispatchSpy.calledOnce);
});
diff --git a/src/containers/Home/style.scss b/src/containers/Home/style.scss
index 20214df..7165036 100644
--- a/src/containers/Home/style.scss
+++ b/src/containers/Home/style.scss
@@ -3,4 +3,26 @@
width: 70%;
margin: 0 auto;
}
+
+ .posts__total {
+ margin-left: 15%;
+ margin-bottom: 1rem;
+ border: 1px solid #eeeeee;
+ display: flex;
+ width: 70%;
+ padding: 1rem;
+ }
+
+ .posts__small {
+ font-size: 0.5rem;
+ background-color: #202860;
+ color: white;
+ padding: 0.1rem 0.2rem;
+ margin-right: 1rem;
+ }
+
+ .posts__dummy {
+ margin-left: 5rem;
+ margin-right: 1rem;
+ }
}
diff --git a/src/entries/template.js b/src/entries/template.js
index 27ecc27..ca1a380 100644
--- a/src/entries/template.js
+++ b/src/entries/template.js
@@ -2,8 +2,15 @@ import React from 'react';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
+import { hot, setConfig } from 'react-hot-loader';
import configureStore from '../configureStore';
+setConfig({
+ ignoreSFC: true, // RHL will be __complitely__ disabled for SFC
+ // pureSFC: true,
+ // pureRender: true, // RHL will not change render method
+});
+
/**
* you can REUSE this template for different entries
*
@@ -25,12 +32,19 @@ export default function mount(Routes, rootReducer) {
// Create Redux store with initial state
const store = configureStore(reduxState, rootReducer);
+ function App() {
+ return (
+
+
+
+
+
+ );
+ }
+ const HotApp = hot(module)(App);
+
hydrate(
-
-
-
-
- ,
+ ,
document.getElementById('app-mount-point'),
);
}
diff --git a/src/hooks/useDummy.js b/src/hooks/useDummy.js
new file mode 100644
index 0000000..74f737c
--- /dev/null
+++ b/src/hooks/useDummy.js
@@ -0,0 +1,10 @@
+import { useRedux } from './useRedux';
+import { dummy as dummyAction } from '../actions';
+
+export default function useDummy() {
+ const [state, dispatch] = useRedux();
+ const triggerDummy = () => {
+ dispatch(dummyAction());
+ };
+ return [state.pages.home.howManyDummies, triggerDummy];
+}
diff --git a/src/hooks/useRedux.js b/src/hooks/useRedux.js
new file mode 100644
index 0000000..3e586a2
--- /dev/null
+++ b/src/hooks/useRedux.js
@@ -0,0 +1,11 @@
+// react is very likely to provide an official hook for redux
+// https://reactjs.org/docs/hooks-faq.html#what-do-hooks-mean-for-popular-apis-like-redux-connect-and-react-router
+import { useContext } from 'react';
+import { ReactReduxContext } from 'react-redux';
+
+export function useRedux() {
+ const { storeState: state, store: { dispatch } } = useContext(ReactReduxContext);
+ return [state, dispatch];
+}
+
+export default useRedux;
diff --git a/src/reducers/pages/home.js b/src/reducers/pages/home.js
index 2387eef..4ca2124 100644
--- a/src/reducers/pages/home.js
+++ b/src/reducers/pages/home.js
@@ -7,6 +7,7 @@ const debug = stdout('reducer:home');
const initialState = {
count: 0,
posts: [],
+ howManyDummies: 0,
};
export default function homeReducer(state = initialState, action) {
@@ -43,6 +44,14 @@ export default function homeReducer(state = initialState, action) {
return state;
}
+ case 'DUMMY_ACTION': {
+ return update(state, {
+ howManyDummies: {
+ $set: state.howManyDummies + 1,
+ },
+ });
+ }
+
default:
return state;
}
diff --git a/webpack.browser.babel.js b/webpack.browser.babel.js
index d775076..990dd98 100644
--- a/webpack.browser.babel.js
+++ b/webpack.browser.babel.js
@@ -68,7 +68,7 @@ export default function browserConfig(env) {
const devEntry = {};
Object.keys(entry).forEach((key) => {
devEntry[key] = [];
- devEntry[key].push('react-hot-loader/patch', entry[key], hotMiddlewareScript);
+ devEntry[key].push(entry[key], hotMiddlewareScript);
});
config.entry = devEntry;
diff --git a/yarn.lock b/yarn.lock
index 25bb45c..f620c32 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2990,6 +2990,10 @@ eslint-plugin-jsx-a11y@^6.1.1:
has "^1.0.3"
jsx-ast-utils "^2.0.1"
+eslint-plugin-react-hooks@^0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-0.0.0.tgz#9988f14082a159931c3dfa9ba699130457da927a"
+
eslint-plugin-react@^7.11.1:
version "7.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c"
@@ -3854,7 +3858,7 @@ hoist-non-react-statics@^2.5.0:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
-hoist-non-react-statics@^3.1.0:
+hoist-non-react-statics@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz#42414ccdfff019cd2168168be998c7b3bd5245c0"
dependencies:
@@ -6623,14 +6627,14 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-dom@^16.4.1:
- version "16.6.3"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0"
+react-dom@^16.7.0-alpha.2:
+ version "16.7.0-alpha.2"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.2.tgz#16632880ed43676315991d8b412cce6975a30282"
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
- scheduler "^0.11.2"
+ scheduler "^0.12.0-alpha.2"
react-helmet@^5.2.0:
version "5.2.0"
@@ -6641,36 +6645,38 @@ react-helmet@^5.2.0:
prop-types "^15.5.4"
react-side-effect "^1.1.0"
-react-hot-loader@^4.3.12:
- version "4.3.12"
- resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.3.12.tgz#0d56688884e7330c63a00a17217866280616b07a"
+react-hot-loader@^4.5.1:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.5.1.tgz#4a9cf136542b1db4f948ac74b31fa16a411ceebb"
dependencies:
fast-levenshtein "^2.0.6"
global "^4.3.0"
hoist-non-react-statics "^2.5.0"
+ loader-utils "^1.1.0"
+ lodash.merge "^4.6.1"
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
shallowequal "^1.0.2"
+ source-map "^0.7.3"
react-is@^16.3.2, react-is@^16.6.0, react-is@^16.6.1, react-is@^16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0"
-react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
+react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
-react-redux@^5.0.6:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.1.tgz#88e368682c7fa80e34e055cd7ac56f5936b0f52f"
+react-redux@^6.0.0-beta.2:
+ version "6.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.0-beta.2.tgz#90b8d50ae71d9dc5d413200836d279de64f7c3e2"
dependencies:
"@babel/runtime" "^7.1.2"
- hoist-non-react-statics "^3.1.0"
+ hoist-non-react-statics "^3.0.1"
invariant "^2.2.4"
loose-envify "^1.1.0"
- prop-types "^15.6.1"
+ prop-types "^15.6.2"
react-is "^16.6.0"
- react-lifecycles-compat "^3.0.0"
react-router-dom@^4.3.1:
version "4.3.1"
@@ -6711,14 +6717,14 @@ react-test-renderer@^16.0.0-0:
react-is "^16.6.3"
scheduler "^0.11.2"
-react@^16.4.1:
- version "16.6.3"
- resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c"
+react@^16.7.0-alpha.2:
+ version "16.7.0-alpha.2"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.7.0-alpha.2.tgz#924f2ae843a46ea82d104a8def7a599fbf2c78ce"
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
- scheduler "^0.11.2"
+ scheduler "^0.12.0-alpha.2"
read-pkg-up@^1.0.1:
version "1.0.1"
@@ -7172,6 +7178,13 @@ scheduler@^0.11.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
+scheduler@^0.12.0-alpha.2:
+ version "0.12.0-alpha.2"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0-alpha.2.tgz#2a8bc8dc6ecdb75fa6480ceeedc1f187c9539970"
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
schema-utils@^0.4.4:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
@@ -7441,6 +7454,10 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+source-map@^0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
+
spawn-wrap@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c"