Skip to content

Commit

Permalink
Merge branch 'universal-redux' into fullstack
Browse files Browse the repository at this point in the history
  • Loading branch information
diegohaz committed Dec 16, 2016
2 parents 2f1f4f8 + d78284d commit 0c3833a
Show file tree
Hide file tree
Showing 25 changed files with 259 additions and 206 deletions.
15 changes: 3 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ export default PostList
```js
import React, { PropTypes, Component } from 'react'
import { connect } from 'react-redux'
import { postList, fromPost, fromStatus, POST_LIST } from 'store'
import { fromPost, fromStatus } from 'store/selectors'
import { postList, POST_LIST } from 'store/actions'

import { PostList } from 'components'

Expand Down Expand Up @@ -186,17 +187,7 @@ Here lives all the state management of the app.
- `selectors` are used by the application to get parts of the current state. [Learn more](http://redux.js.org/docs/recipes/ComputingDerivedData.html);
- `sagas` listen to the actions and are responsible for performing side effects, like data fetching, caching etc. [Learn more](https://github.com/yelouafi/redux-saga).

To add a new store, just create a new folder with a reducer and change the `store/index.js` file:
```js
import post from './post/reducer'
import status from './status/reducer'

const reducers = {
routing,
form,
post,
status
}
To add a new store, just create a new folder with actions, reducer, selectors and/or sagas. Webpack will automatically import them to your project (how? See [`src/store/actions.js`](src/store/actions.js), [`src/store/reducer.js`](src/store/reducer.js), [`src/store/sagas.js`](src/store/sagas.js) and [`src/store/selectors.js`](src/store/selectors.js)).
```
### Universal
Expand Down
19 changes: 18 additions & 1 deletion src-clean/components/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component, PropTypes } from 'react'
import { injectGlobal } from 'styled-components'
import Helmet from 'react-helmet'

injectGlobal`
body {
Expand All @@ -15,7 +16,23 @@ class App extends Component {
render () {
const { children } = this.props
return (
<div>{children}</div>
<div>
<Helmet
title="Atomic React"
titleTemplate="ARc - %s"
meta={[
{ name: 'description', content: 'React starter kit based on Atomic Design with React Router v4, Webpack, Redux, Server Side Rendering and more.' },
{ property: 'og:site_name', content: 'ARc' },
{ property: 'og:image', content: 'https://diegohaz.github.io/arc/thumbnail.png' },
{ property: 'og:image:type', content: 'image/png' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' }
]}
link={[
{ rel: 'icon', href: 'https://diegohaz.github.io/arc/icon.png' }
]} />
{children}
</div>
)
}
}
Expand Down
5 changes: 4 additions & 1 deletion src-clean/components/Html.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ const Html = ({ styles, assets, state, content }) => {
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{helmet.link.toComponent()}
<style dangerouslySetInnerHTML={{ __html: styles }} />
</head>
<body>
<main id="app" dangerouslySetInnerHTML={{ __html: content }} />
<script dangerouslySetInnerHTML={{ __html: state }} />
<script src={assets.javascript.main} />
{Object.keys(assets.javascript).map((key) =>
<script key={key} src={assets.javascript[key]} />
)}
</body>
</html>
)
Expand Down
Empty file.
20 changes: 20 additions & 0 deletions src-clean/components/globals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const colors = {
primary: ['#1976d2', '#2196f3', '#71bcf7', '#c2e2fb'],
secondary: ['#c2185b', '#e91e63', '#f06292', '#f8bbd0'],
danger: ['#d32f2f', '#f44336', '#f8877f', '#ffcdd2'],
alert: ['#ffa000', '#ffc107', '#ffd761', '#ffecb3'],
success: ['#388e3c', '#4caf50', '#7cc47f', '#c8e6c9'],
grayscale: ['#212121', '#616161', '#9e9e9e', '#bdbdbd', '#e0e0e0', '#eeeeee', '#ffffff']
}

export const reverseColors = {}

Object.keys(colors).forEach((key) => {
reverseColors[key] = [ ...colors[key] ].reverse()
})

export const fonts = {
primary: 'Helvetica Neue, Helvetica, Roboto, sans-serif',
pre: 'Consolas, Liberation Mono, Menlo, Courier, monospace',
quote: 'Georgia, serif'
}
13 changes: 6 additions & 7 deletions src-clean/components/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from './atoms'
export * from './molecules'
export * from './organisms'
export * from './templates'
export * from './pages'
export App from './App'
export Html from './Html'
const req = require.context('.', true, /\.\/[^/]+\/[^/]+\/index\.js$/)

req.keys().forEach((key) => {
const componentName = key.replace(/^.+\/([^/]+)\/index\.js/, '$1')
module.exports[componentName] = req(key).default
})
Empty file.
Empty file.
1 change: 0 additions & 1 deletion src-clean/components/pages/index.js

This file was deleted.

1 change: 0 additions & 1 deletion src-clean/components/templates/index.js

This file was deleted.

6 changes: 6 additions & 0 deletions src-clean/containers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const req = require.context('.', false, /^((?!index).)*$/)

req.keys().forEach((key) => {
const containerName = key.replace(/^\.\/([^.]+)\.js$/, '$1')
module.exports[containerName] = req(key).default
})
3 changes: 2 additions & 1 deletion src-clean/routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react'
import { Route, IndexRoute } from 'react-router'

import { App, HomePage } from 'components'
import App from 'components/App'
import { HomePage } from 'components'

const routes = (
<Route path="/" component={App}>
Expand Down
8 changes: 7 additions & 1 deletion src-clean/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import serialize from 'serialize-javascript'
import styleSheet from 'styled-components/lib/models/StyleSheet'
import cors from 'cors'
import csrf from 'csurf'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import { Provider } from 'react-redux'
import { createMemoryHistory, RouterContext, match } from 'react-router'
Expand All @@ -13,14 +14,17 @@ import api from 'api'
import routes from 'routes'
import configureStore from 'store/configure'
import { env, port, ip, mongo } from 'config'
import { Html } from 'components'
import { setCsrfToken } from 'store/actions'
import Html from 'components/Html'

const router = new Router()

mongoose.connect(mongo.uri)

router.use('/api', cors(), api)

router.use(csrf({ cookie: true }))

router.use((req, res, next) => {
if (env === 'development') {
global.webpackIsomorphicTools.refresh()
Expand All @@ -30,6 +34,8 @@ router.use((req, res, next) => {
const store = configureStore({}, memoryHistory)
const history = syncHistoryWithStore(memoryHistory, store)

store.dispatch(setCsrfToken(req.csrfToken()))

match({ history, routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search)
Expand Down
11 changes: 11 additions & 0 deletions src-clean/store/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import forIn from 'lodash/forIn'

const req = require.context('.', true, /\.\/.+\/actions\.js$/)

req.keys().forEach((key) => {
const actions = req(key)

forIn(actions, (action, name) => {
module.exports[name] = action
})
})
17 changes: 12 additions & 5 deletions src-clean/store/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import createSagaMiddleware from 'redux-saga'
import { reducer, saga } from '.'
import reducer from './reducer'
import sagas from './sagas'

const configureStore = (initialState, history) => {
const hasWindow = typeof window !== 'undefined'
Expand All @@ -14,14 +15,20 @@ const configureStore = (initialState, history) => {
)(createStore)

const store = finalCreateStore(reducer, initialState)

sagaMiddleware.run(saga)
let sagaTask = sagaMiddleware.run(sagas)

if (module.hot) {
module.hot.accept('.', () => {
const nextReducer = require('.').reducer
module.hot.accept('./reducer', () => {
const nextReducer = require('./reducer').default
store.replaceReducer(nextReducer)
})
module.hot.accept('./sagas', () => {
const nextSagas = require('./sagas').default
sagaTask.cancel()
sagaTask.done.then(() => {
sagaTask = sagaMiddleware.run(nextSagas)
})
})
}

return store
Expand Down
56 changes: 0 additions & 56 deletions src-clean/store/index.js

This file was deleted.

17 changes: 17 additions & 0 deletions src-clean/store/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { combineReducers } from 'redux'
import { routerReducer as routing } from 'react-router-redux'
import { reducer as form } from 'redux-form'

const reducers = {
routing,
form
}

const req = require.context('.', true, /\.\/.+\/reducer\.js$/)

req.keys().forEach((key) => {
const storeName = key.replace(/\.\/(.+)\/.+$/, '$1')
reducers[storeName] = req(key).default
})

export default combineReducers(reducers)
13 changes: 13 additions & 0 deletions src-clean/store/sagas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { fork } from 'redux-saga/effects'

const req = require.context('.', true, /\.\/.+\/sagas\.js$/)

const sagas = []

req.keys().forEach((key) => {
sagas.push(req(key).default)
})

export default function* () {
yield sagas.map(fork)
}
19 changes: 19 additions & 0 deletions src-clean/store/selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import upperFirst from 'lodash/upperFirst'
import forIn from 'lodash/forIn'

const req = require.context('.', true, /\.\/.+\/selectors\.js$/)

req.keys().forEach((key) => {
const storeName = key.replace(/\.\/(.+)\/.+$/, '$1')
const fromName = `from${upperFirst(storeName)}`
const selectors = req(key)
const getState = (state = {}) => state[storeName] || {}

module.exports[fromName] = {}

forIn(selectors, (selector, name) => {
if (typeof selector === 'function') {
module.exports[fromName][name] = (state, ...args) => selector(getState(state), ...args)
}
})
})
4 changes: 3 additions & 1 deletion src/components/Html.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const Html = ({ styles, assets, state, content }) => {
<body>
<main id="app" dangerouslySetInnerHTML={{ __html: content }} />
<script dangerouslySetInnerHTML={{ __html: state }} />
<script src={assets.javascript.main} />
{Object.keys(assets.javascript).map((key) =>
<script key={key} src={assets.javascript[key]} />
)}
</body>
</html>
)
Expand Down
5 changes: 1 addition & 4 deletions src/store/generic/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ test('genericCreate', () => {
})

test('genericUpdate', () => {
expect(actions.genericUpdate(
{ id: 1 },
{ title: 'test2' }
)).toEqual({
expect(actions.genericUpdate({ id: 1 }, { title: 'test2' })).toEqual({
type: actions.GENERIC_UPDATE,
data: {
id: 1
Expand Down
12 changes: 8 additions & 4 deletions src/store/generic/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ export default (state = initialState, action) => {
}
case GENERIC_UPDATE:
case GENERIC_DELETE:
return find(state, action)
return findReducer(state, action)
default:
return state
}
}

const find = (state, action) => {
const index = findIndex(state.list, action.data)
const findReducer = (state, action) => {
const isObject = typeof action.data === 'object'
const index = isObject ? findIndex(state.list, action.data) : state.list.indexOf(action.data)

if (index < 0) {
return state
}
Expand All @@ -29,7 +31,9 @@ const find = (state, action) => {
...state,
list: [
...state.list.slice(0, index),
{ ...state.list[index], ...action.newData },
typeof action.data === 'object'
? { ...state.list[index], ...action.newData }
: action.newData,
...state.list.slice(index + 1)
]
}
Expand Down
Loading

0 comments on commit 0c3833a

Please sign in to comment.