mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Add redux. Shared actions can be found from _src/ducks/_ folder and route specific are inside container folder.
This commit is contained in:
parent
826fe88dd6
commit
a5e051a7ee
12 changed files with 215 additions and 23 deletions
|
|
@ -11,8 +11,10 @@
|
|||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"react-helmet": "^4.0.0",
|
||||
"react-redux": "^5.0.2",
|
||||
"react-router": "4.0.0-alpha.6",
|
||||
"react-test-renderer": "^15.4.2",
|
||||
"redux": "^3.6.0",
|
||||
"sharetribe-scripts": "0.8.6",
|
||||
"source-map-support": "^0.4.10"
|
||||
},
|
||||
|
|
|
|||
35
src/app.js
35
src/app.js
|
|
@ -2,30 +2,45 @@ import React, { PropTypes } from 'react';
|
|||
import ReactDOMServer from 'react-dom/server';
|
||||
import Helmet from 'react-helmet';
|
||||
import { BrowserRouter, ServerRouter } from 'react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from './store';
|
||||
import Routes from './Routes';
|
||||
|
||||
const RoutesWithRouterProp = ({ router }) => <Routes router={router} />;
|
||||
export const ClientApp = props => {
|
||||
const { store } = props;
|
||||
return (
|
||||
<BrowserRouter>
|
||||
{
|
||||
({router}) => (
|
||||
<Provider store={store}>
|
||||
<Routes router={router} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const { any, string } = PropTypes;
|
||||
|
||||
RoutesWithRouterProp.propTypes = { router: any.isRequired };
|
||||
|
||||
export const ClientApp = () => (
|
||||
<BrowserRouter>
|
||||
{RoutesWithRouterProp}
|
||||
</BrowserRouter>
|
||||
);
|
||||
ClientApp.propTypes = { store: any.isRequired };
|
||||
|
||||
export const ServerApp = props => {
|
||||
const { url, context } = props;
|
||||
return (
|
||||
<ServerRouter location={url} context={context}>
|
||||
{RoutesWithRouterProp}
|
||||
{
|
||||
({router}) => (
|
||||
<Provider store={store}>
|
||||
<Routes router={router} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
</ServerRouter>
|
||||
);
|
||||
};
|
||||
|
||||
ServerApp.propTypes = { url: string.isRequired, context: any.isRequired };
|
||||
ServerApp.propTypes = { url: string.isRequired, context: any.isRequired, store: any.isRequired };
|
||||
|
||||
/**
|
||||
* Render the given route.
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ import ReactDOMServer from 'react-dom/server';
|
|||
import { forEach } from 'lodash';
|
||||
import { createServerRenderContext } from 'react-router';
|
||||
import { ClientApp, ServerApp } from './app';
|
||||
import configureStore from './store';
|
||||
|
||||
const store = configureStore({});
|
||||
|
||||
const render = (url, context) =>
|
||||
ReactDOMServer.renderToString(<ServerApp url={url} context={context} />);
|
||||
ReactDOMServer.renderToString(<ServerApp url={url} context={context} store={store} />);
|
||||
|
||||
describe('Application', () => {
|
||||
it('renders in the client without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<ClientApp />, div);
|
||||
ReactDOM.render(<ClientApp store={store} />, div);
|
||||
});
|
||||
|
||||
it('renders in the server without crashing', () => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { Page } from '../../components';
|
||||
import { addFlashNotification } from '../../ducks/FlashNotification';
|
||||
import { addFilter } from './SearchPageDucks';
|
||||
|
||||
export default () => (
|
||||
<Page title="Search page">
|
||||
export const SearchPage = props => (
|
||||
<PageLayout title="Search page">
|
||||
<Link to="/l/Nice+studio-in-Helsinki-345">Nice studio in Helsinki</Link>
|
||||
</Page>
|
||||
)
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
/**
|
||||
* Container functions.
|
||||
*/
|
||||
const mapStateToProps = function mapStateToProps(state) {
|
||||
return state;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
addNotice: msg => dispatch(addFlashNotification('notice', msg)),
|
||||
addFilter: (k, v) => dispatch(addFilter(k, v)),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SearchPage);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter } from 'react-router';
|
||||
import renderer from 'react-test-renderer';
|
||||
import SearchPage from './SearchPage';
|
||||
import { SearchPage } from './SearchPage';
|
||||
|
||||
describe('SearchPage', () => {
|
||||
it('matches snapshot', () => {
|
||||
const component = renderer.create(
|
||||
(
|
||||
<BrowserRouter>
|
||||
<SearchPage />
|
||||
</BrowserRouter>
|
||||
),
|
||||
<BrowserRouter>
|
||||
<SearchPage />
|
||||
</BrowserRouter>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
|
|||
26
src/containers/SearchPage/SearchPageDucks.js
Normal file
26
src/containers/SearchPage/SearchPageDucks.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* This file contains Action constants, Action creators, and reducer of a page
|
||||
* container. We are following Ducks module proposition:
|
||||
* https://github.com/erikras/ducks-modular-redux
|
||||
*/
|
||||
import unionBy from 'lodash/unionBy';
|
||||
|
||||
// Actions
|
||||
const ADD_FILTER = 'ADD_FILTER';
|
||||
|
||||
// Reducer
|
||||
export default function reducer(state = {}, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case ADD_FILTER:
|
||||
const stateFilters = state.filters || [];
|
||||
return Object.assign({}, state, { filters: unionBy(stateFilters, [ payload ]) });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// Action Creators
|
||||
export const addFilter = (key, value) => {
|
||||
return { type: ADD_FILTER, payload: { [key]: value } };
|
||||
};
|
||||
9
src/containers/reducers.js
Normal file
9
src/containers/reducers.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Export reducers from ducks modules of different containers (i.e. default export)
|
||||
* We are following Ducks module proposition:
|
||||
* https://github.com/erikras/ducks-modular-redux
|
||||
*/
|
||||
|
||||
import SearchPage from './SearchPage/SearchPageDucks';
|
||||
|
||||
export { SearchPage };
|
||||
52
src/ducks/FlashNotification.js
Normal file
52
src/ducks/FlashNotification.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* This file contains Action constants, Action creators, and reducer of global
|
||||
* FlashMessages. Global actions can be used in multiple pages.
|
||||
* We are following Ducks module proposition:
|
||||
* https://github.com/erikras/ducks-modular-redux
|
||||
*/
|
||||
|
||||
import find from 'lodash/find';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
|
||||
// Actions: system notifications
|
||||
const ADD_FLASH_NOTIFICATION = 'FLASH::ADD_NOTIFICATION';
|
||||
const REMOVE_FLASH_NOTIFICATION = 'FLASH::REMOVE_NOTIFICATION';
|
||||
|
||||
const initialState = [];
|
||||
|
||||
// Reducer
|
||||
export default (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case ADD_FLASH_NOTIFICATION:
|
||||
if (!find(state, n => n.content === payload.content && !n.isRead)) {
|
||||
return state.concat([
|
||||
{ id: payload.id, type: payload.type, content: payload.content, isRead: false },
|
||||
]);
|
||||
}
|
||||
return state;
|
||||
|
||||
case REMOVE_FLASH_NOTIFICATION:
|
||||
return state.map(
|
||||
findIndex(state, msg => msg.id === payload.id),
|
||||
msg => msg.set('isRead', true),
|
||||
);
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
let nextMessageId = 1;
|
||||
|
||||
export const addFlashNotification = (type, content) => {
|
||||
const id = nextMessageId;
|
||||
nextMessageId += 1;
|
||||
return {
|
||||
type: ADD_FLASH_NOTIFICATION,
|
||||
payload: { id: `note_${id}`, type, content, isRead: false },
|
||||
};
|
||||
};
|
||||
|
||||
export const removeFlashNotification = id => ({ type: REMOVE_FLASH_NOTIFICATION, payload: { id } });
|
||||
9
src/ducks/index.js
Normal file
9
src/ducks/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Import reducers from shared ducks modules (default export)
|
||||
* We are following Ducks module proposition:
|
||||
* https://github.com/erikras/ducks-modular-redux
|
||||
*/
|
||||
|
||||
import FlashNotification from './FlashNotification';
|
||||
|
||||
export { FlashNotification };
|
||||
14
src/reducers.js
Normal file
14
src/reducers.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import * as globalReducers from './ducks';
|
||||
import * as pageReducers from './containers/reducers';
|
||||
|
||||
/**
|
||||
* Function _createReducer_ combines global reducers (reducers that are used in
|
||||
* multiple pages) and reducers that are handling actions happening inside one page container.
|
||||
* Future: this structure could take in asyncReducers, which are changed when you navigate pages.
|
||||
*/
|
||||
const createReducer = function createReducer() {
|
||||
return combineReducers({ ...globalReducers, ...pageReducers });
|
||||
};
|
||||
|
||||
export default createReducer;
|
||||
10
src/store.js
Normal file
10
src/store.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { createStore } from 'redux';
|
||||
import createReducer from './reducers';
|
||||
|
||||
/**
|
||||
* configureStore creates a new store with initial state and possible enhancers
|
||||
* (like redux-saga or redux-thunk middleware)
|
||||
*/
|
||||
export default function configureStore(initialState) {
|
||||
return createStore(createReducer(), initialState);
|
||||
}
|
||||
37
yarn.lock
37
yarn.lock
|
|
@ -2608,6 +2608,10 @@ hoek@2.x.x:
|
|||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
|
||||
hoist-non-react-statics@^1.0.3:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||
|
|
@ -2801,7 +2805,7 @@ interpret@^1.0.0:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c"
|
||||
|
||||
invariant@^2.2.0, invariant@^2.2.1:
|
||||
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
|
||||
dependencies:
|
||||
|
|
@ -3455,6 +3459,14 @@ loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.
|
|||
json5 "^0.5.0"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
lodash, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.2, lodash@^4.16.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
lodash-es@^4.2.0, lodash-es@^4.2.1:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
|
||||
|
||||
lodash._arraycopy@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1"
|
||||
|
|
@ -4869,6 +4881,16 @@ react-helmet@^4.0.0:
|
|||
object-assign "^4.0.1"
|
||||
react-side-effect "^1.1.0"
|
||||
|
||||
react-redux@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.2.tgz#3d9878f5f71c6fafcd45de1fbb162ea31f389814"
|
||||
dependencies:
|
||||
hoist-non-react-statics "^1.0.3"
|
||||
invariant "^2.0.0"
|
||||
lodash "^4.2.0"
|
||||
lodash-es "^4.2.0"
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
react-router@4.0.0-alpha.6:
|
||||
version "4.0.0-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.0.0-alpha.6.tgz#239fcf9a6ba7997021022c9b51d72d370f7b6bf4"
|
||||
|
|
@ -5010,6 +5032,15 @@ reduce-function-call@^1.0.1:
|
|||
dependencies:
|
||||
balanced-match "^0.4.2"
|
||||
|
||||
redux@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
|
||||
dependencies:
|
||||
lodash "^4.2.1"
|
||||
lodash-es "^4.2.1"
|
||||
loose-envify "^1.1.0"
|
||||
symbol-observable "^1.0.2"
|
||||
|
||||
referrer-policy@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79"
|
||||
|
|
@ -5598,6 +5629,10 @@ svgo@^0.7.0:
|
|||
sax "~1.2.1"
|
||||
whet.extend "~0.9.9"
|
||||
|
||||
symbol-observable@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
|
||||
|
||||
"symbol-tree@>= 3.1.0 < 4.0.0":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.1.tgz#8549dd1d01fa9f893c18cc9ab0b106b4d9b168cb"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue