Skip to content

Commit

Permalink
Refactor with redux observable (vercel#13615)
Browse files Browse the repository at this point in the history
Related to [11014](vercel#11014)

1. Moved the reducer into the store and created new store file
2. The example was using a server that was no longer available, now it uses JSON placeholder instead.
3. Moved from getInitialProps to getStaticProps
4. Refactored all the classes to functional components, using the new redux hooks API.
5. Upgraded all the packages and using custom redux wrapper instead of next-redux-wrapper, which I have removed from the example.
6. Upgraded all the other packages.

Please, let me know if I should change anything.
  • Loading branch information
todortotev authored and rokinsky committed Jul 11, 2020
1 parent 79b7d8b commit f18a5a8
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 225 deletions.
19 changes: 0 additions & 19 deletions examples/with-redux-observable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,6 @@ yarn dev

Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

### Notes

The main problem with integrating Redux, Redux-Observable and Next.js is
probably making initial requests on a server. That's because, the
`getInitialProps` hook runs on the server-side before epics have been made available by just dispatching actions.

However, we can access and execute epics directly. In order to do so, we need to
pass them an Observable of an action together with StateObservable and they will return an Observable:

```js
static async getInitialProps({ store, isServer }) {
const state$ = new StateObservable(new Subject(), store.getState());
const resultAction = await rootEpic(
of(actions.fetchCharacter(isServer)),
state$
).toPromise(); // we need to convert Observable to Promise
store.dispatch(resultAction)};
```

Note: we are not using `AjaxObservable` from the `rxjs` library; as of rxjs
v5.5.6, it will not work on both the server- and client-side. Instead we call
the default export from
Expand Down
44 changes: 0 additions & 44 deletions examples/with-redux-observable/components/CharacterInfo.js

This file was deleted.

50 changes: 50 additions & 0 deletions examples/with-redux-observable/components/UserInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useSelector } from 'react-redux'

const useUser = () => {
return useSelector((state) => ({
character: state.character,
error: state.error,
isFetchedOnServer: state.isFetchedOnServer,
}))
}

const UserInfo = () => {
const { character, isFetchedOnServer, error } = useUser()
const { name, id, username, email, phone, website } = character

return (
<div className="UserInfo">
{error ? (
<p>We encountered and error.</p>
) : (
<article>
<h3>Name: {name}</h3>
<p>Id: {id}</p>
<p>Username: {username}</p>
<p>Email: {email}</p>
<p>Phone: {phone}</p>
<p>Website: {website}</p>
</article>
)}
<p>
(was user fetched on server? - <b>{isFetchedOnServer.toString()})</b>
</p>
<p> Please note there are no more than 10 users in the API!</p>
<style jsx>{`
article {
background-color: #528ce0;
border-radius: 15px;
padding: 15px;
width: 250px;
margin: 15px 0;
color: white;
}
button {
margin-right: 10px;
}
`}</style>
</div>
)
}

export default UserInfo
Binary file removed examples/with-redux-observable/demo.png
Binary file not shown.
18 changes: 8 additions & 10 deletions examples/with-redux-observable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
"build": "next build",
"start": "next start"
},
"author": "tomaszmularczyk([email protected])",
"dependencies": {
"next": "latest",
"next-redux-wrapper": "^2.0.0-beta.6",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"redux-logger": "^3.0.6",
"redux-observable": "^1.0.0",
"rxjs": "^6.3.3",
"universal-rxjs-ajax": "^2.0.0"
"react": "16.13.1",
"react-dom": "16.13.1",
"react-redux": "7.2.0",
"redux": "4.0.5",
"redux-logger": "3.0.6",
"redux-observable": "1.2.0",
"rxjs": "6.5.5",
"universal-rxjs-ajax": "2.0.4"
},
"license": "ISC"
}
29 changes: 8 additions & 21 deletions examples/with-redux-observable/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
import { Provider } from 'react-redux'
import App from 'next/app'
import withRedux from 'next-redux-wrapper'
import makeStore from '../redux'
import { useStore } from '../store/store'

class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {}
export default function App({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState)

return { pageProps }
}

render() {
const { Component, pageProps, store } = this.props
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}

export default withRedux(makeStore)(MyApp)
69 changes: 25 additions & 44 deletions examples/with-redux-observable/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,30 @@
import { Component } from 'react'
import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import Link from 'next/link'
import { of, Subject } from 'rxjs'
import { StateObservable } from 'redux-observable'
import { connect } from 'react-redux'
import CharacterInfo from '../components/CharacterInfo'
import { rootEpic } from '../redux/epics'
import * as actions from '../redux/actions'
import UserInfo from '../components/UserInfo'
import { stopFetchingUsers, startFetchingUsers } from '../store/actions'

class Counter extends Component {
static async getInitialProps({ store, isServer }) {
const state$ = new StateObservable(new Subject(), store.getState())
const resultAction = await rootEpic(
of(actions.fetchCharacter(isServer)),
state$
).toPromise() // we need to convert Observable to Promise
store.dispatch(resultAction)
const Counter = () => {
const dispatch = useDispatch()

return { isServer }
}
useEffect(() => {
dispatch(startFetchingUsers())
return () => {
dispatch(stopFetchingUsers())
}
}, [dispatch])

componentDidMount() {
this.props.startFetchingCharacters()
}

componentWillUnmount() {
this.props.stopFetchingCharacters()
}

render() {
return (
<div>
<h1>Index Page</h1>
<CharacterInfo />
<br />
<nav>
<Link href="/other">
<a>Navigate to "/other"</a>
</Link>
</nav>
</div>
)
}
return (
<div>
<h1>Index Page</h1>
<UserInfo />
<br />
<nav>
<Link href="/other">
<a>Navigate to "/other"</a>
</Link>
</nav>
</div>
)
}

export default connect(null, {
startFetchingCharacters: actions.startFetchingCharacters,
stopFetchingCharacters: actions.stopFetchingCharacters,
})(Counter)
export default Counter
5 changes: 0 additions & 5 deletions examples/with-redux-observable/redux/actionTypes.js

This file was deleted.

21 changes: 0 additions & 21 deletions examples/with-redux-observable/redux/actions.js

This file was deleted.

16 changes: 0 additions & 16 deletions examples/with-redux-observable/redux/index.js

This file was deleted.

28 changes: 0 additions & 28 deletions examples/with-redux-observable/redux/reducer.js

This file was deleted.

5 changes: 5 additions & 0 deletions examples/with-redux-observable/store/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const FETCH_USER = 'FETCH_USER'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE'
export const START_FETCHING_USERS = 'START_FETCHING_USERS'
export const STOP_FETCHING_USERS = 'STOP_FETCHING_USERS'
21 changes: 21 additions & 0 deletions examples/with-redux-observable/store/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as types from './actionTypes'

export const startFetchingUsers = () => ({
type: types.START_FETCHING_USERS,
})
export const stopFetchingUsers = () => ({
type: types.STOP_FETCHING_USERS,
})
export const fetchUser = (isServer = false) => ({
type: types.FETCH_USER,
payload: { isServer },
})
export const fetchUserSuccess = (response, isServer) => ({
type: types.FETCH_USER_SUCCESS,
payload: { response, isServer },
})

export const fetchUserFailure = (error, isServer) => ({
type: types.FETCH_USER_FAILURE,
payload: { error, isServer },
})
Loading

0 comments on commit f18a5a8

Please sign in to comment.