Skip to content

Commit

Permalink
Create with-redux-persist example (#7375)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrusso1020 authored and Luis Fernando Alvarez D committed May 23, 2019
1 parent bb46c06 commit ae8363c
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 0 deletions.
54 changes: 54 additions & 0 deletions examples/with-redux-persist/README.md
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`
22 changes: 22 additions & 0 deletions examples/with-redux-persist/components/clock.js
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
47 changes: 47 additions & 0 deletions examples/with-redux-persist/components/counter.js
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)
70 changes: 70 additions & 0 deletions examples/with-redux-persist/components/data-list.js
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)
21 changes: 21 additions & 0 deletions examples/with-redux-persist/components/examples.js
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)
50 changes: 50 additions & 0 deletions examples/with-redux-persist/lib/with-redux-store.js
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} />
}
}
}
19 changes: 19 additions & 0 deletions examples/with-redux-persist/package.json
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"
}
28 changes: 28 additions & 0 deletions examples/with-redux-persist/pages/_app.js
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)
33 changes: 33 additions & 0 deletions examples/with-redux-persist/pages/index.js
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)
Loading

0 comments on commit ae8363c

Please sign in to comment.