-
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.
add example with mobx version 6 and mobx react lite (#17493)
Mobx version 6 has been released, and it's a big one. <blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="en" dir="ltr">Just released <a href="https://twitter.com/hashtag/mobx?src=hash&ref_src=twsrc%5Etfw">#mobx</a> 6! <br><br>👉 makeAutoObservable 😍<br>👉 Decorator free by default<br>👉 Fully revamped docs for modern React <br>👉 Supersedes both MobX 4 and 5<br>👉 Codemod for migration<a href="https://t.co/U6EpZaNhyz">https://t.co/U6EpZaNhyz</a></p>— Michel Weststrate (@mweststrate) <a href="https://twitter.com/mweststrate/status/1311344102991159296?ref_src=twsrc%5Etfw">September 30, 2020</a></blockquote> Decorator support is officially dropped, so the syntax for creating observable objects has changed (checkout store.js). There is no need for custom babel configuration anymore. In comparison to current mobx examples, the difference is that I'm using regular `React.useContext` and `React.createContext` to consume the mobx store, [this is recommended by the official documentation.](https://mobx.js.org/react-integration.html#using-external-state-in-observer-components) When the component is wrapped in the observer function, the component function is given a name so it appears correctly in the react development tools. As of mobx v6 `mobx-react` package bundles `mobx-react-lite` so I could have used that package, but I've decided to use the `lite` one, because of the size.
- Loading branch information
Showing
10 changed files
with
147 additions
and
131 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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
{ | ||
"presets": ["next/babel"] | ||
"presets": ["next/babel"], | ||
"plugins": [["@babel/plugin-proposal-class-properties", { "loose": false }]] | ||
} |
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
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
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 |
---|---|---|
@@ -1,30 +1,34 @@ | ||
import { useObserver } from 'mobx-react-lite' | ||
import { observer } from 'mobx-react-lite' | ||
import Link from 'next/link' | ||
import { useContext, useEffect } from 'react' | ||
import { StoreContext, start, stop } from '../store' | ||
import { useEffect } from 'react' | ||
import Clock from './Clock' | ||
import { useStore } from './StoreProvider' | ||
|
||
function Page({ linkTo, title }) { | ||
const store = useContext(StoreContext) | ||
const Page = observer(function Page(props) { | ||
// use store from the store context | ||
const store = useStore() | ||
|
||
//start the clock when the component is mounted | ||
useEffect(() => { | ||
start() | ||
return stop | ||
}, []) | ||
store.start() | ||
|
||
// stop the clock when the component unmounts | ||
return () => { | ||
store.stop() | ||
} | ||
}, [store]) | ||
|
||
return ( | ||
<div> | ||
<h1>{title}</h1> | ||
{useObserver(() => ( | ||
<Clock lastUpdate={store.lastUpdate} light={store.light} /> | ||
))} | ||
<h1>{props.title}</h1> | ||
<Clock /> | ||
<nav> | ||
<Link href={linkTo}> | ||
<Link href={props.linkTo}> | ||
<a>Navigate</a> | ||
</Link> | ||
</nav> | ||
</div> | ||
) | ||
} | ||
}) | ||
|
||
export default Page |
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,36 @@ | ||
import { createContext, useContext } from 'react' | ||
import { Store } from '../store' | ||
|
||
let store | ||
export const StoreContext = createContext() | ||
|
||
export function useStore() { | ||
const context = useContext(StoreContext) | ||
if (context === undefined) { | ||
throw new Error('useStore must be used within StoreProvider') | ||
} | ||
|
||
return context | ||
} | ||
|
||
export function StoreProvider({ children, initialState: initialData }) { | ||
const store = initializeStore(initialData) | ||
|
||
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider> | ||
} | ||
|
||
function initializeStore(initialData = null) { | ||
const _store = store ?? new Store() | ||
|
||
// If your page has Next.js data fetching methods that use a Mobx store, it will | ||
// get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details | ||
if (initialData) { | ||
_store.hydrate(initialData) | ||
} | ||
// For SSG and SSR always create a new store | ||
if (typeof window === 'undefined') return _store | ||
// Create the store once in the client | ||
if (!store) store = _store | ||
|
||
return _store | ||
} |
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
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 |
---|---|---|
@@ -1,11 +1,9 @@ | ||
import { InjectStoreContext } from '../store' | ||
import { StoreProvider } from '../components/StoreProvider' | ||
|
||
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={pageProps.initialStoreData}> | ||
<StoreProvider {...pageProps}> | ||
<Component {...pageProps} /> | ||
</InjectStoreContext> | ||
</StoreProvider> | ||
) | ||
} |
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,11 @@ | ||
import Page from '../components/Page' | ||
|
||
export default function SSG() { | ||
return <Page title="Index Page" linkTo="/other" /> | ||
} | ||
|
||
// If you build and start the app, the date returned here will have the same | ||
// value for all requests, as this method gets executed at build time. | ||
export function getStaticProps() { | ||
return { props: { initialState: { lastUpdate: Date.now() } } } | ||
} |
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,12 @@ | ||
import Page from '../components/Page' | ||
|
||
export default function SSR() { | ||
return <Page title="Index Page" linkTo="/other" /> | ||
} | ||
|
||
// The date returned here will be different for every request that hits the page, | ||
// that is because the page becomes a serverless function instead of being statically | ||
// exported when you use `getServerSideProps` or `getInitialProps` | ||
export function getServerSideProps() { | ||
return { props: { initialState: { lastUpdate: Date.now() } } } | ||
} |
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 |
---|---|---|
@@ -1,44 +1,45 @@ | ||
import { action } from 'mobx' | ||
import { useObservable, useStaticRendering } from 'mobx-react-lite' | ||
import { createContext, useCallback } from 'react' | ||
|
||
const isServer = typeof window === 'undefined' | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
useStaticRendering(isServer) | ||
|
||
let StoreContext = createContext() | ||
let start | ||
let stop | ||
let store | ||
|
||
function initializeData(initialData = store || {}) { | ||
const { lastUpdate = Date.now(), light } = initialData | ||
return { | ||
lastUpdate, | ||
light: Boolean(light), | ||
} | ||
} | ||
import { action, observable, computed, runInAction, makeObservable } from 'mobx' | ||
import { enableStaticRendering } from 'mobx-react-lite' | ||
|
||
enableStaticRendering(typeof window === 'undefined') | ||
|
||
function InjectStoreContext({ children, initialData }) { | ||
let timerInterval = null | ||
store = useObservable(initializeData(initialData)) | ||
export class Store { | ||
lastUpdate = 0 | ||
light = false | ||
|
||
start = useCallback( | ||
action(() => { | ||
timerInterval = setInterval(() => { | ||
store.lastUpdate = Date.now() | ||
store.light = true | ||
}, 1000) | ||
constructor() { | ||
makeObservable(this, { | ||
lastUpdate: observable, | ||
light: observable, | ||
start: action, | ||
timeString: computed, | ||
}) | ||
) | ||
} | ||
|
||
stop = () => { | ||
if (timerInterval) { | ||
clearInterval(timerInterval) | ||
} | ||
start = () => { | ||
this.timer = setInterval(() => { | ||
runInAction(() => { | ||
this.lastUpdate = Date.now() | ||
this.light = true | ||
}) | ||
}, 1000) | ||
} | ||
|
||
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider> | ||
} | ||
get timeString() { | ||
const pad = (n) => (n < 10 ? `0${n}` : n) | ||
const format = (t) => | ||
`${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad( | ||
t.getUTCSeconds() | ||
)}` | ||
return format(new Date(this.lastUpdate)) | ||
} | ||
|
||
stop = () => clearInterval(this.timer) | ||
|
||
hydrate = (data) => { | ||
if (!data) return | ||
|
||
export { InjectStoreContext, StoreContext, initializeData, start, stop, store } | ||
this.lastUpdate = data.lastUpdate !== null ? data.lastUpdate : Date.now() | ||
this.light = !!data.light | ||
} | ||
} |