-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create with-redux-persist example (#7375)
- Loading branch information
1 parent
bb46c06
commit ae8363c
Showing
10 changed files
with
442 additions
and
0 deletions.
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,54 @@ | ||
[![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-persist) | ||
|
||
# Redux Persist 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-persist with-redux-persist-app | ||
# or | ||
yarn create next-app --example with-redux-persist with-redux-persist-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-persist | ||
cd with-redux-persist | ||
``` | ||
|
||
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 with the power of Redux Persist in Next.js. | ||
|
||
With the advantage of having a global state for your app using `redux`. You'll also require some of your state values to be available offline. There comes `redux-persist` using which you can persist your states in browser's local storage. While there are various ways of persisting your states which you can always find in there [documentation](https://github.com/rt2zz/redux-persist/blob/master/README.md). This is an example of how you can integrate `redux-persist` with redux along with Next.js's universal rendering approach. | ||
|
||
In this example, we are going to use the Next.js example [with-redux](https://github.com/zeit/next.js/tree/master/examples/with-redux-persist) to see how you can add a layer of persistence for one of the state from global redux state. To know more about how to create a Next.js project with Redux, you can browse the example project [with-redux](https://github.com/zeit/next.js/tree/master/examples/with-redux) to know more about its implementation. | ||
|
||
The Redux Persist has been initialized in `store.js`. You can modify the `redux-persist` configuration (In this example, we are persisting only one state termed `exampleData` which is added in the `persist configuration`, listed under the values of whitelist states) if you need something more with `redux-persist` by following their [docs](https://github.com/rt2zz/redux-persist/blob/master/README.md). To wrap out our component in the `Persist Gate` which rehydrates the global state with combining the persisted values and global state values, we'll have to make some modifications in the implementation of Redux in `pages/_app.js`. | ||
|
||
The example under `components/data-list.js`, shows a simple component that fetches data after being mounted and then dispatches an action to populate the redux state `exampleData` with the fetched data. And in `store.js`, since we have whitelisted `exampleData` state to be persisted, So once the redux state receives the persisted data from browser's local storage, it will be then updated to the global redux state. So if you open the app next time and there is no Internet connection or whatsoever condition, the app will load the persisted data and will render it on the screen. | ||
|
||
For simplicity and readability, Reducers, Actions, Redux Persist configuration, 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,22 @@ | ||
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 => t.toJSON().slice(11, 19) // cut off except hh:mm:ss |
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,47 @@ | ||
import React, { Component } from 'react' | ||
import { connect } from 'react-redux' | ||
import { bindActionCreators } from 'redux' | ||
import { incrementCount, decrementCount, resetCount } from '../store' | ||
|
||
class Counter extends Component { | ||
increment = () => { | ||
const { incrementCount } = this.props | ||
incrementCount() | ||
} | ||
|
||
decrement = () => { | ||
const { decrementCount } = this.props | ||
decrementCount() | ||
} | ||
|
||
reset = () => { | ||
const { resetCount } = this.props | ||
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 } | ||
} | ||
const mapDispatchToProps = dispatch => | ||
bindActionCreators({ incrementCount, decrementCount, resetCount }, dispatch) | ||
|
||
export default connect( | ||
mapStateToProps, | ||
mapDispatchToProps | ||
)(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,70 @@ | ||
|
||
import React, { Component } from 'react' | ||
import { connect } from 'react-redux' | ||
import { bindActionCreators } from 'redux' | ||
import { loadExampleData, loadingExampleDataFailure } from '../store' | ||
|
||
class DataList extends Component { | ||
state = { | ||
isDataLoading: false | ||
} | ||
|
||
componentDidMount () { | ||
const { loadExampleData, loadingExampleDataFailure } = this.props | ||
const self = this | ||
|
||
this.setState({ isDataLoading: true }) | ||
window.fetch('https://jsonplaceholder.typicode.com/users') | ||
.then( | ||
function (response) { | ||
if (response.status !== 200) { | ||
console.log('Looks like there was a problem. Status Code: ' + | ||
response.status) | ||
loadingExampleDataFailure() | ||
self.setState({ isDataLoading: false }) | ||
return | ||
} | ||
response.json().then(function (data) { | ||
loadExampleData(data) | ||
self.setState({ isDataLoading: false }) | ||
}) | ||
} | ||
) | ||
.catch(function (err) { | ||
console.log('Fetch Error :-S', err) | ||
loadingExampleDataFailure() | ||
self.setState({ isDataLoading: false }) | ||
}) | ||
} | ||
|
||
render () { | ||
const { exampleData, error } = this.props | ||
const { isDataLoading } = this.state | ||
|
||
return ( | ||
<div> | ||
<h1> | ||
API DATA: | ||
</h1> | ||
{exampleData && !isDataLoading ? ( | ||
<pre> | ||
<code>{JSON.stringify(exampleData, null, 2)}</code> | ||
</pre> | ||
) : (<p style={{ color: 'blue' }}>Loading...</p>) } | ||
{error && <p style={{ color: 'red' }}>Error fetching data.</p>} | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
function mapStateToProps (state) { | ||
const { exampleData, error } = state | ||
return { exampleData, error } | ||
} | ||
const mapDispatchToProps = dispatch => | ||
bindActionCreators({ loadExampleData, loadingExampleDataFailure }, dispatch) | ||
|
||
export default connect( | ||
mapStateToProps, | ||
mapDispatchToProps | ||
)(DataList) |
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,21 @@ | ||
import { connect } from 'react-redux' | ||
import Clock from './clock' | ||
import Counter from './counter' | ||
import DataList from './data-list' | ||
|
||
function Examples ({ lastUpdate, light }) { | ||
return ( | ||
<div> | ||
<Clock lastUpdate={lastUpdate} light={light} /> | ||
<Counter /> | ||
<DataList /> | ||
</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,19 @@ | ||
{ | ||
"name": "with-redux", | ||
"version": "1.0.0", | ||
"scripts": { | ||
"dev": "next", | ||
"build": "next build", | ||
"start": "next start" | ||
}, | ||
"dependencies": { | ||
"next": "latest", | ||
"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-persist": "^5.10.0" | ||
}, | ||
"license": "ISC" | ||
} |
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 App, { Container } from 'next/app' | ||
import React from 'react' | ||
import withReduxStore from '../lib/with-redux-store' | ||
import { Provider } from 'react-redux' | ||
import { persistStore } from 'redux-persist' | ||
import { PersistGate } from 'redux-persist/integration/react' | ||
|
||
class MyApp extends App { | ||
constructor (props) { | ||
super(props) | ||
this.persistor = persistStore(props.reduxStore) | ||
} | ||
|
||
render () { | ||
const { Component, pageProps, reduxStore } = this.props | ||
return ( | ||
<Container> | ||
<Provider store={reduxStore}> | ||
<PersistGate loading={<Component {...pageProps} />} persistor={this.persistor}> | ||
<Component {...pageProps} /> | ||
</PersistGate> | ||
</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,33 @@ | ||
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 | ||
// DISPATCH ACTIONS HERE ONLY WITH `reduxStore.dispatch` | ||
reduxStore.dispatch(serverRenderClock(isServer)) | ||
|
||
return {} | ||
} | ||
|
||
componentDidMount () { | ||
// DISPATCH ACTIONS HERE FROM `mapDispatchToProps` | ||
// TO TICK THE CLOCK | ||
this.timer = setInterval(() => this.props.startClock(), 1000) | ||
} | ||
|
||
componentWillUnmount () { | ||
clearInterval(this.timer) | ||
} | ||
|
||
render () { | ||
return <Examples /> | ||
} | ||
} | ||
const mapDispatchToProps = { startClock } | ||
export default connect( | ||
null, | ||
mapDispatchToProps | ||
)(Index) |
Oops, something went wrong.