Skip to content

Commit

Permalink
updated zustand to 4.3.6, changed out deprecated methods, converted t…
Browse files Browse the repository at this point in the history
…o typescript
  • Loading branch information
Mojtaba-NA committed Mar 8, 2023
1 parent 9a1798b commit b729278
Show file tree
Hide file tree
Showing 23 changed files with 186 additions and 165 deletions.
3 changes: 3 additions & 0 deletions examples/with-zustand/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
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.

17 changes: 12 additions & 5 deletions examples/with-zustand/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
{
"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",
"eslint": "8.35.0",
"eslint-config-next": "latest",
"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
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
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
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,10 @@
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
60 changes: 60 additions & 0 deletions examples/with-zustand/src/lib/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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>
)
}

0 comments on commit b729278

Please sign in to comment.