Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updated zustand example to 4.3.6, changed out deprecated methods #46911

Merged
merged 4 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions examples/with-zustand/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ Usually splitting your app state into `pages` feels natural but sometimes you'll

In the first 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 (black) than the client one (grey).

To illustrate SSG and SSR, go to `/ssg` and `/ssr`, those pages are using Next.js data fetching methods to get the date in the server and return it as props to the page, and then the browser will hydrate the store and continue updating the date.
To illustrate SSG, go to `/ssg` and to illustrate SSR go to `/`, those pages are using Next.js data fetching methods to get the date in the server and return it as props to the page, and then the browser will hydrate the store and continue updating the date.

The trick here for supporting universal Zustand 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 trick here for supporting universal Zustand 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 with the `initialZustandState` returned from the get\*Props methods.

All components have access to the Zustand store using `useStore()` returned from zustand's `createContext()` function.

On the server side every request initializes a new store, because otherwise different user data can be mixed up. On the client side the same store is used, even between page changes.
All components have access to the Zustand store using `useStore()` returned `store.ts` file.

## Deploy your own

Expand Down
26 changes: 0 additions & 26 deletions examples/with-zustand/components/nav.js

This file was deleted.

85 changes: 0 additions & 85 deletions examples/with-zustand/lib/store.js

This file was deleted.

15 changes: 10 additions & 5 deletions examples/with-zustand/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{
"private": true,
"scripts": {
"dev": "next",
"dev": "next dev",
"build": "next build",
"start": "next start"
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@types/node": "18.14.6",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zustand": "^3.7.1"
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "4.9.5",
"zustand": "^4.3.6"
}
}
10 changes: 0 additions & 10 deletions examples/with-zustand/pages/_app.js

This file was deleted.

5 changes: 0 additions & 5 deletions examples/with-zustand/pages/index.js

This file was deleted.

Binary file added examples/with-zustand/public/favicon.ico
Binary file not shown.
1 change: 1 addition & 0 deletions examples/with-zustand/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/with-zustand/public/thirteen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/with-zustand/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useStore } from '../lib/store'
import shallow from 'zustand/shallow'

const useClock = () => {
return useStore(
(store) => ({ lastUpdate: store.lastUpdate, light: store.light }),
shallow
)
return useStore((store) => ({
lastUpdate: store.lastUpdate,
light: store.light,
}))
}

const formatTime = (time) => {
const formatTime = (time: number) => {
// cut off except hh:mm:ss
return new Date(time).toJSON().slice(11, 19)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { useStore } from '../lib/store'
import shallow from 'zustand/shallow'
const useCounter = () => {
const { count, increment, decrement, reset } = useStore(
(store) => ({
count: store.count,
increment: store.increment,
decrement: store.decrement,
reset: store.reset,
}),
shallow
)

return { count, increment, decrement, reset }
const useCounter = () => {
return useStore((store) => ({
count: store.count,
increment: store.increment,
decrement: store.decrement,
reset: store.reset,
}))
}

const Counter = () => {
Expand Down
19 changes: 19 additions & 0 deletions examples/with-zustand/src/components/nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Link from 'next/link'
import { CSSProperties } from 'react'

const LinkStyle: CSSProperties = { marginRight: '25px' }

const Nav = () => {
return (
<nav>
<Link href="/ssg" style={LinkStyle}>
SSG
</Link>
<Link href="/" style={LinkStyle}>
SSR
</Link>
</nav>
)
}

export default Nav
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Nav from './nav'
import { useStore } from '../lib/store'

export default function Page() {
const { tick } = useStore()
const tick = useStore((store) => store.tick)

// Tick the time every second
useInterval(() => {
Expand Down
15 changes: 15 additions & 0 deletions examples/with-zustand/src/lib/StoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type PropsWithChildren, useRef } from 'react'
import type { StoreType } from './store'
import { initializeStore, Provider } from './store'

const StoreProvider = ({ children, ...props }: PropsWithChildren) => {
const storeRef = useRef<StoreType>()

if (!storeRef.current) {
storeRef.current = initializeStore(props)
}

return <Provider value={storeRef.current}>{children}</Provider>
}

export default StoreProvider
62 changes: 62 additions & 0 deletions examples/with-zustand/src/lib/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createContext, useContext } from 'react'
import { createStore, useStore as useZustandStore } from 'zustand'

interface StoreInterface {
lastUpdate: number
light: boolean
count: number
tick: (lastUpdate: number, light: boolean) => void
increment: () => void
decrement: () => void
reset: () => void
}

const getDefaultInitialState = () => ({
lastUpdate: Date.now(),
light: false,
count: 0,
})

export type StoreType = ReturnType<typeof initializeStore>

const zustandContext = createContext<StoreType | null>(null)

export const Provider = zustandContext.Provider

export const useStore = <T>(selector: (state: StoreInterface) => T) => {
const store = useContext(zustandContext)

if (!store) throw new Error('Store is missing the provider')

return useZustandStore(store, selector)
}

export const initializeStore = (
preloadedState: Partial<StoreInterface> = {}
) => {
return createStore<StoreInterface>((set, get) => ({
...getDefaultInitialState(),
...preloadedState,
tick: (lastUpdate, light) => {
set({
lastUpdate,
light: !!light,
})
},
increment: () => {
set({
count: get().count + 1,
})
},
decrement: () => {
set({
count: get().count - 1,
})
},
reset: () => {
set({
count: getDefaultInitialState().count,
})
},
}))
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useEffect, useRef } from 'react'

// https://overreacted.io/making-setinterval-declarative-with-react-hooks/
const useInterval = (callback, delay) => {
const savedCallback = useRef()
const useInterval = (callback: () => void, delay: number | undefined) => {
const savedCallback = useRef<typeof callback>()

useEffect(() => {
savedCallback.current = callback
}, [callback])

useEffect(() => {
const handler = (...args) => savedCallback.current(...args)
const handler = () => savedCallback.current?.()

if (delay !== null) {
const id = setInterval(handler, delay)
Expand Down
10 changes: 10 additions & 0 deletions examples/with-zustand/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import StoreProvider from '@/lib/StoreProvider'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
return (
<StoreProvider {...pageProps.initialZustandState}>
<Component {...pageProps} />
</StoreProvider>
)
}
13 changes: 13 additions & 0 deletions examples/with-zustand/src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import type { GetServerSideProps } from 'next'
import Page from '../components/page'
import { initializeStore } from '../lib/store'

export default function SSR() {
export default function Home() {
return <Page />
}

// 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() {
export const getServerSideProps: GetServerSideProps = async () => {
const zustandStore = initializeStore()

return {
props: {
// the "stringify and then parse again" piece is required as next.js
Expand Down
Loading