-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added frontend, as Next.js and Redux
Signed-off-by: Varij Kapil <varijkapil13@live.com>
- Loading branch information
1 parent
42f0504
commit 6e99c7f
Showing
11 changed files
with
5,953 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
[](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-redux) | ||
|
||
# Redux example | ||
|
||
## How to use | ||
|
||
### Using `create-next-app` | ||
|
||
Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: | ||
|
||
```bash | ||
npx create-next-app --example with-redux with-redux-app | ||
# or | ||
yarn create next-app --example with-redux with-redux-app | ||
``` | ||
|
||
### Download manually | ||
|
||
Download the example: | ||
|
||
```bash | ||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-redux | ||
cd with-redux | ||
``` | ||
|
||
Install it and run: | ||
|
||
```bash | ||
npm install | ||
npm run dev | ||
# or | ||
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 shows how to integrate Redux in Next.js. | ||
|
||
Usually splitting your app state into `pages` feels natural but sometimes you'll want to have global state for your app. This is an example on how you can use redux that also works with Next.js's universal rendering approach. | ||
|
||
In the first example we are going to display a digital clock that updates every second. The first render is happening in the server and then the browser will take over. To illustrate this, the server rendered clock will have a different background color (black) than the client one (grey). | ||
|
||
The Redux `Provider` is implemented in `pages/_app.js`. Since the `MyApp` component is wrapped in `withReduxStore` the redux store will be automatically initialized and provided to `MyApp`, which in turn passes it off to `react-redux`'s `Provider` component. | ||
|
||
All pages have access to the redux store using `connect` from `react-redux`. | ||
|
||
On the server side every request initializes a new store, because otherwise different user data can be mixed up. On the client side the same store is used, even between page changes. | ||
|
||
The example under `components/counter.js`, shows a simple incremental counter implementing a common Redux pattern of mapping state to props. Again, the first render is happening in the server and instead of starting the count at 0, it will dispatch an action in redux that starts the count at 1. This continues to highlight how each navigation triggers a server render first and then a client render when switching pages on the client side | ||
|
||
For simplicity and readability, Reducers, Actions, and Store creators are all in the same file: `store.js` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export default ({lastUpdate, light}) => { | ||
return ( | ||
<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> | ||
); | ||
}; | ||
|
||
const format = t => `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}`; | ||
|
||
const pad = n => (n < 10 ? `0${n}` : n); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React, {Component} from 'react'; | ||
import {connect} from 'react-redux'; | ||
import {incrementCount, decrementCount, resetCount} from '../store'; | ||
|
||
class Counter extends Component { | ||
increment = () => { | ||
const {dispatch} = this.props; | ||
dispatch(incrementCount()); | ||
}; | ||
|
||
decrement = () => { | ||
const {dispatch} = this.props; | ||
dispatch(decrementCount()); | ||
}; | ||
|
||
reset = () => { | ||
const {dispatch} = this.props; | ||
dispatch(resetCount()); | ||
}; | ||
|
||
render() { | ||
const {count} = this.props; | ||
return ( | ||
<div> | ||
<h1> | ||
Count: <span>{count}</span> | ||
</h1> | ||
<button onClick={this.increment}>+1</button> | ||
<button onClick={this.decrement}>-1</button> | ||
<button onClick={this.reset}>Reset</button> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
function mapStateToProps(state) { | ||
const {count} = state; | ||
return {count}; | ||
} | ||
|
||
export default connect(mapStateToProps)(Counter); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import {connect} from 'react-redux'; | ||
import Clock from './clock'; | ||
import Counter from './counter'; | ||
|
||
function Examples({lastUpdate, light}) { | ||
return ( | ||
<div> | ||
<Clock lastUpdate={lastUpdate} light={light} /> | ||
<Counter /> | ||
</div> | ||
); | ||
} | ||
|
||
function mapStateToProps(state) { | ||
const {lastUpdate, light} = state; | ||
return {lastUpdate, light}; | ||
} | ||
|
||
export default connect(mapStateToProps)(Examples); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React from 'react'; | ||
import {initializeStore} from '../store'; | ||
|
||
const isServer = typeof window === 'undefined'; | ||
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'; | ||
|
||
function getOrCreateStore(initialState) { | ||
// Always make a new store if server, otherwise state is shared between requests | ||
if (isServer) { | ||
return initializeStore(initialState); | ||
} | ||
|
||
// Create store if unavailable on the client and set it on the window object | ||
if (!window[__NEXT_REDUX_STORE__]) { | ||
window[__NEXT_REDUX_STORE__] = initializeStore(initialState); | ||
} | ||
return window[__NEXT_REDUX_STORE__]; | ||
} | ||
|
||
export default App => { | ||
return class AppWithRedux extends React.Component { | ||
static async getInitialProps(appContext) { | ||
// Get or Create the store with `undefined` as initialState | ||
// This allows you to set a custom default initialState | ||
const reduxStore = getOrCreateStore(); | ||
|
||
// Provide the store to getInitialProps of pages | ||
appContext.ctx.reduxStore = reduxStore; | ||
|
||
let appProps = {}; | ||
if (typeof App.getInitialProps === 'function') { | ||
appProps = await App.getInitialProps(appContext); | ||
} | ||
|
||
return { | ||
...appProps, | ||
initialReduxState: reduxStore.getState() | ||
}; | ||
} | ||
|
||
constructor(props) { | ||
super(props); | ||
this.reduxStore = getOrCreateStore(props.initialReduxState); | ||
} | ||
|
||
render() { | ||
return <App {...this.props} reduxStore={this.reduxStore} />; | ||
} | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "employee-manager-frontend", | ||
"version": "1.0.0", | ||
"private": true, | ||
"scripts": { | ||
"dev": "next", | ||
"build": "next build", | ||
"start": "next start" | ||
}, | ||
"dependencies": { | ||
"next": "^7.0.2", | ||
"react": "^16.7.0", | ||
"react-dom": "^16.7.0", | ||
"react-redux": "^5.0.1", | ||
"redux": "^3.6.0", | ||
"redux-devtools-extension": "^2.13.2", | ||
"redux-thunk": "^2.1.0" | ||
}, | ||
"devDependencies": { | ||
"husky": "^1.3.1", | ||
"lint-staged": "^8.1.0", | ||
"nodemon": "^1.18.9", | ||
"prettier": "^1.15.3" | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"prettier --single-quote --print-width 150 --jsx-bracket-same-line --parser flow --no-bracket-spacing --write", | ||
"git add" | ||
] | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"license": "MIT" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import App, {Container} from 'next/app'; | ||
import React from 'react'; | ||
import withReduxStore from '../lib/with-redux-store'; | ||
import {Provider} from 'react-redux'; | ||
|
||
class MyApp extends App { | ||
render() { | ||
const {Component, pageProps, reduxStore} = this.props; | ||
return ( | ||
<Container> | ||
<Provider store={reduxStore}> | ||
<Component {...pageProps} /> | ||
</Provider> | ||
</Container> | ||
); | ||
} | ||
} | ||
|
||
export default withReduxStore(MyApp); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React from 'react'; | ||
import {connect} from 'react-redux'; | ||
import {startClock, serverRenderClock} from '../store'; | ||
import Examples from '../components/examples'; | ||
|
||
class Index extends React.Component { | ||
static getInitialProps({reduxStore, req}) { | ||
const isServer = !!req; | ||
reduxStore.dispatch(serverRenderClock(isServer)); | ||
|
||
return {}; | ||
} | ||
|
||
componentDidMount() { | ||
const {dispatch} = this.props; | ||
this.timer = startClock(dispatch); | ||
} | ||
|
||
componentWillUnmount() { | ||
clearInterval(this.timer); | ||
} | ||
|
||
render() { | ||
return <Examples />; | ||
} | ||
} | ||
|
||
export default connect()(Index); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import {createStore, applyMiddleware} from 'redux'; | ||
import {composeWithDevTools} from 'redux-devtools-extension'; | ||
import thunkMiddleware from 'redux-thunk'; | ||
|
||
const exampleInitialState = { | ||
lastUpdate: 0, | ||
light: false, | ||
count: 0 | ||
}; | ||
|
||
export const actionTypes = { | ||
TICK: 'TICK', | ||
INCREMENT: 'INCREMENT', | ||
DECREMENT: 'DECREMENT', | ||
RESET: 'RESET' | ||
}; | ||
|
||
// REDUCERS | ||
export const reducer = (state = exampleInitialState, action) => { | ||
switch (action.type) { | ||
case actionTypes.TICK: | ||
return Object.assign({}, state, { | ||
lastUpdate: action.ts, | ||
light: !!action.light | ||
}); | ||
case actionTypes.INCREMENT: | ||
return Object.assign({}, state, { | ||
count: state.count + 1 | ||
}); | ||
case actionTypes.DECREMENT: | ||
return Object.assign({}, state, { | ||
count: state.count - 1 | ||
}); | ||
case actionTypes.RESET: | ||
return Object.assign({}, state, { | ||
count: exampleInitialState.count | ||
}); | ||
default: | ||
return state; | ||
} | ||
}; | ||
|
||
// ACTIONS | ||
export const serverRenderClock = isServer => dispatch => { | ||
return dispatch({type: actionTypes.TICK, light: !isServer, ts: Date.now()}); | ||
}; | ||
|
||
export const startClock = dispatch => { | ||
return setInterval(() => { | ||
// Dispatch `TICK` every 1 second | ||
dispatch({type: actionTypes.TICK, light: true, ts: Date.now()}); | ||
}, 1000); | ||
}; | ||
|
||
export const incrementCount = () => dispatch => { | ||
return dispatch({type: actionTypes.INCREMENT}); | ||
}; | ||
|
||
export const decrementCount = () => dispatch => { | ||
return dispatch({type: actionTypes.DECREMENT}); | ||
}; | ||
|
||
export const resetCount = () => dispatch => { | ||
return dispatch({type: actionTypes.RESET}); | ||
}; | ||
|
||
export function initializeStore(initialState = exampleInitialState) { | ||
return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware))); | ||
} |
Oops, something went wrong.