Skip to content

Commit

Permalink
Refactor with redux observable (#13615)
Browse files Browse the repository at this point in the history
Related to [11014](#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 committed Jun 3, 2020
1 parent bad3761 commit 34f82a9
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
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
@@ -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
Expand Up @@ -6,18 +6,16 @@
"build": "next build",
"start": "next start"
},
"author": "tomaszmularczyk(tomasz.mularczyk89@gmail.com)",
"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
@@ -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
@@ -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
@@ -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
@@ -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 },
})

1 comment on commit 34f82a9

@oste
Copy link
Contributor

@oste oste commented on 34f82a9 Oct 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@todortotev you mentioned 3. Moved from getInitialProps to getStaticProps but I don't see getStaticProps. I am looking to implement a good way to set the initial state on the server so will try to follow up but if you have something in mind please add it here.

Please sign in to comment.