[Examples] Update mobx examples to not use SSR (#11576)

* Updated with-custom-reverse-proxy

* Updated readme of with-env-from-next-config-js

* Updated the kea example

* Updated with-mobx

* Updated with-mobx readme

* Updated the with-mobx-react-lite example
This commit is contained in:
Luis Alvarez D 2020-04-07 08:48:13 -05:00 committed by GitHub
parent 84b89c8b24
commit 1239d5af59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 113 additions and 177 deletions

View file

@ -4,8 +4,9 @@
"dependencies": {
"express": "^4.15.3",
"next": "latest",
"react": "^16.7.0",
"react-dom": "^16.7.0"
"react": "16.13.1",
"react-dom": "16.13.1",
"swr": "0.2.0"
},
"devDependencies": {
"cross-env": "^5.0.1",

View file

@ -1,44 +1,35 @@
import React from 'react'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import useSWR from 'swr'
export default class extends React.Component {
constructor(props) {
super(props)
this.state = { response: '' }
}
const fetcher = url => fetch(url).then(res => res.json())
static async getInitialProps({ pathname, query }) {
return {
pathname,
query,
queryString: Object.keys(query).join(''),
}
}
const useMounted = () => {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
return mounted
}
async componentDidMount() {
const response = JSON.stringify(
await window
.fetch(`/api/${this.props.queryString}`)
.then(response => response.json().then(data => data)),
null,
2
)
this.setState({ response })
}
export default function Index() {
const mounted = useMounted()
const router = useRouter()
const queryString = `/api/${Object.keys(router.query).join('')}`
const { data, error } = useSWR(() => (mounted ? queryString : null), fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
render() {
return (
<content>
<p>
/api/{this.props.queryString} routed to https://swapi.co/api/
{this.props.queryString}
{queryString} routed to https://swapi.co{queryString}
</p>
<p>
<a href="?people/2">Try</a>
&nbsp;
<a href="/">Reset</a>
</p>
<pre>{this.state.response ? this.state.response : 'Loading...'}</pre>
<pre>{data ? JSON.stringify(data, null, 2) : 'Loading...'}</pre>
</content>
)
}
}

View file

@ -2,7 +2,7 @@
This example demonstrates setting parameters that will be used by your application and set at build time (not run time).
More specifically, what that means, is that environmental variables are programmed into the special configuration file `next.js.config` and then
returned to your react components (including `getInitialProps`) when the program is built with `next build`.
returned to your react components when the program is built with `next build`.
As the build process (`next build`) is proceeding, `next.config.js` is processed and passed in as a parameter is the variable `phase`.
`phase` can have the values `PHASE_DEVELOPMENT_SERVER` or `PHASE_PRODUCTION_BUILD` (as defined in `next\constants`). Based on the variable

View file

@ -20,5 +20,18 @@
"license": "ISC",
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.1.0"
},
"babel": {
"presets": [
"next/babel"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
}

View file

@ -28,12 +28,7 @@ const logic = kea({
})
@logic
export default class App extends React.Component {
static getInitialProps({ store }) {
// Start with counter === 10
store.dispatch(logic.actions.increment(10))
}
class Index extends React.Component {
render() {
return (
<div>
@ -48,3 +43,5 @@ export default class App extends React.Component {
)
}
}
export default Index

View file

@ -2,6 +2,12 @@
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 MobX that also works with our universal rendering approach. This is just a way you can do it but it's not the only one.
In this 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 than the client one.
![](http://i.imgur.com/JCxtWSj.gif)
This example is a mobx-react-lite port of the [with-mobx](https://github.com/zeit/next.js/tree/master/examples/with-mobx) example. MobX support has been implemented using React Hooks.
## Deploy your own
Deploy the example using [ZEIT Now](https://zeit.co/now):
@ -41,18 +47,6 @@ yarn dev
Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
## Notes
In this 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 than the client one.
![](http://i.imgur.com/JCxtWSj.gif)
This example is a mobx-react-lite port of the [with-mobx](https://github.com/zeit/next.js/tree/master/examples/with-mobx) example. MobX support has been implemented using React Hooks.
Our page is located at `pages/index.js` so it will map the route `/`. To get the initial data for rendering we are implementing the static method `getInitialProps`, initializing the MobX store and returning the initial timestamp to be rendered. The root component for the render method is a React context provider that allows us to send the store down to children components so they can access to the state when required.
To pass the initial timestamp from the server to the client we pass it as a prop called `lastUpdate` so then it's available when the client takes over.
## Inplementation details
The initial store data is returned from the `initializeData` function that recycles existing store data if it already exists.

View file

@ -1,30 +1,11 @@
import App from 'next/app'
import React from 'react'
import { InjectStoreContext, initializeData } from '../store'
import { InjectStoreContext } from '../store'
class MyMobxApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {}
const initialStoreData = initializeData()
// Provide the store to getInitialProps of pages
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ ...ctx, initialStoreData })
}
return {
pageProps,
initialStoreData,
}
}
render() {
const { Component, pageProps, initialStoreData } = this.props
export default function App({ Component, pageProps }) {
// If your page has Next.js data fetching methods returning a state for the Mobx store,
// then you can hydrate it here.
return (
<InjectStoreContext initialData={initialStoreData}>
<InjectStoreContext initialData={pageProps.initialStoreData}>
<Component {...pageProps} />
</InjectStoreContext>
)
}
}
export default MyMobxApp

View file

@ -1,8 +1,5 @@
import React from 'react'
import Page from '../components/Page'
export default class Counter extends React.Component {
render() {
export default function Index() {
return <Page title="Index Page" linkTo="/other" />
}
}

View file

@ -1,8 +1,5 @@
import React from 'react'
import Page from '../components/Page'
export default class Counter extends React.Component {
render() {
export default function Other() {
return <Page title="Other Page" linkTo="/" />
}
}

View file

@ -2,6 +2,24 @@
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 mobx that also works with our universal rendering approach. This is just a way you can do it but it's not the only one.
In this 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 than the client one.
![](http://i.imgur.com/JCxtWSj.gif)
The clock, under `components/Clock.js`, has access to the state using the `inject` and `observer` functions from `mobx-react`. In this case Clock is a direct child from the page but it could be deep down the render tree.
This example is a mobx port of the [with-redux](https://github.com/zeit/next.js/tree/master/examples/with-redux) example. Decorator support is activated by adding a `.babelrc` file at the root of the project:
```json
{
"presets": ["next/babel"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
```
## Deploy your own
Deploy the example using [ZEIT Now](https://zeit.co/now):
@ -41,34 +59,6 @@ yarn dev
Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
## Notes
This example is a mobx port of the [with-redux](https://github.com/zeit/next.js/tree/master/examples/with-redux) example. Decorator support is activated by adding a `.babelrc` file at the root of the project:
```json
{
"presets": ["next/babel"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
```
### Rehydrating with server data
Be aware that data that was used on the server (and provided via `getInitialProps`) will be stringified in order to rehydrate the client with it. That means, if you create a store that is, say, an `ObservableMap` and give it as prop to a page, then the server will render appropriately. But stringifying it for the client will turn the `ObservableMap` to an ordinary JavaScript object (which does not have `Map`-style methods and is not an observable). So it is better to create the store as a normal object and turn it into a `Observable` in the `render()` method. This way both sides have an `Observable` to work with.
## Notes
In this 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 than the client one.
![](http://i.imgur.com/JCxtWSj.gif)
Our page is located at `pages/index.js` so it will map the route `/`. To get the initial data for rendering we are implementing the static method `getInitialProps`, initializing the mobx store and returning the initial timestamp to be rendered. The root component for the render method is the `mobx-react Provider` that allows us to send the store down to children components so they can access to the state when required.
To pass the initial timestamp from the server to the client we pass it as a prop called `lastUpdate` so then it's available when the client takes over.
The trick here for supporting universal mobx is to separate the cases for the client and the server. When we are on the server we want to create a new store every time, otherwise different users data will be mixed up. If we are in the client we want to use always the same store. That's what we accomplish on `store.js`
The clock, under `components/Clock.js`, has access to the state using the `inject` and `observer` functions from `mobx-react`. In this case Clock is a direct child from the page but it could be deep down the render tree.
Be aware that data that was used on the server (and provided via one of Next.js data fetching methods) will be stringified in order to rehydrate the client with it. That means, if you create a store that is, say, an `ObservableMap` and give it as prop to a page, then the server will render appropriately. But stringifying it for the client will turn the `ObservableMap` to an ordinary JavaScript object (which does not have `Map`-style methods and is not an observable). So it is better to create the store as a normal object and turn it into a `Observable` in the `render()` method. This way both sides have an `Observable` to work with.

View file

@ -1,37 +1,24 @@
import App from 'next/app'
import React from 'react'
import { fetchInitialStoreState, Store } from '../store'
import { useMemo, useEffect } from 'react'
import { Store } from '../store'
import { Provider } from 'mobx-react'
class MyMobxApp extends App {
state = {
store: new Store(),
}
export default function App({ Component, pageProps }) {
const store = useMemo(() => {
return new Store()
}, [])
// Fetching serialized(JSON) store state
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext)
const initialStoreState = await fetchInitialStoreState()
return {
...appProps,
initialStoreState,
}
useEffect(() => {
// If your page has Next.js data fetching methods returning a state for the Mobx store,
// then you can hydrate it here.
const { initialState } = pageProps
if (initialState) {
store.hydrate(initialState)
}
}, [store, pageProps])
// Hydrate serialized state to store
static getDerivedStateFromProps(props, state) {
state.store.hydrate(props.initialStoreState)
return state
}
render() {
const { Component, pageProps } = this.props
return (
<Provider store={this.state.store}>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
}
export default MyMobxApp

View file

@ -1,8 +1,5 @@
import React from 'react'
import Page from '../components/Page'
export default class Counter extends React.Component {
render() {
export default function Index() {
return <Page title="Index Page" linkTo="/other" />
}
}

View file

@ -1,8 +1,5 @@
import React from 'react'
import Page from '../components/Page'
export default class Counter extends React.Component {
render() {
export default function Other() {
return <Page title="Other Page" linkTo="/" />
}
}

View file

@ -1,9 +1,8 @@
import { action, observable } from 'mobx'
import { useStaticRendering } from 'mobx-react'
const isServer = typeof window === 'undefined'
// eslint-disable-next-line react-hooks/rules-of-hooks
useStaticRendering(isServer)
useStaticRendering(typeof window === 'undefined')
export class Store {
@observable lastUpdate = 0
@ -26,8 +25,3 @@ export class Store {
stop = () => clearInterval(this.timer)
}
export async function fetchInitialStoreState() {
// You can do anything to fetch initial store state
return {}
}