Skip to content

Commit

Permalink
Update nextjs guide (#2328)
Browse files Browse the repository at this point in the history
* Update nextjs guide

* Minor changes

* Minor changes

* Minor changes

* Minor changes

* Minor changes

* Minor changes
  • Loading branch information
dbritto-dev authored Feb 16, 2024
1 parent eb8443f commit 8c1a1f0
Showing 1 changed file with 158 additions and 36 deletions.
194 changes: 158 additions & 36 deletions docs/guides/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,41 @@ We have these general recommendations for the appropriate use of Zustand:
Let's write our store factory function that will create a new store for each
request.

```json
// tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```

> **Note:** do not forget to remove all comments from your `tsconfig.json` file.
```ts
// stores/counter-store.ts
import { createStore } from 'zustand'
// src/stores/counter-store.ts
import { createStore } from 'zustand/vanilla'

export type CounterState = {
count: number
Expand Down Expand Up @@ -71,14 +103,17 @@ export const createCounterStore = (
Let's use the `createCounterStore` in our component and share it using a context provider.

```tsx
// components/counter-store-provider.tsx
// src/providers/counter-store-provider.tsx
'use client'

import { type ReactNode, createContext, useRef, useContext } from 'react'
import { type StoreApi, useStore } from 'zustand'

import { type CounterStore, createCounterStore } from 'stores/counter-store.ts'
import { type CounterStore, createCounterStore } from '@/stores/counter-store'

export const CounterStoreContext = createContext()
export const CounterStoreContext = createContext<StoreApi<CounterStore> | null>(
null,
)

export interface CounterStoreProviderProps {
children: ReactNode
Expand All @@ -87,7 +122,7 @@ export interface CounterStoreProviderProps {
export const CounterStoreProvider = ({
children,
}: CounterStoreProviderProps) => {
const storeRef = useRef<CounterStore>()
const storeRef = useRef<StoreApi<CounterStore>>()
if (!storeRef.current) {
storeRef.current = createCounterStore()
}
Expand All @@ -99,10 +134,12 @@ export const CounterStoreProvider = ({
)
}

export const useCounterStore = (selector: (store: CounterStore) => T): T => {
export const useCounterStore = <T,>(
selector: (store: CounterStore) => T,
): T => {
const counterStoreContext = useContext(CounterStoreContext)

if (counterStoreContext === undefined) {
if (!counterStoreContext) {
throw new Error(`useCounterStore must be use within CounterStoreProvider`)
}

Expand All @@ -119,30 +156,64 @@ export const useCounterStore = (selector: (store: CounterStore) => T): T => {
### Initializing the store

```ts
// stores/counter-store.ts
// rest of code
// src/stores/counter-store.ts
import { createStore } from 'zustand/vanilla'

export type CounterState = {
count: number
}

export type CounterActions = {
decrementCount: () => void
incrementCount: () => void
}

export type CounterStore = CounterState & CounterActions

export const initCounterStore = (): CounterState => {
return { count: new Date().getFullYear() }
}

export const defaultInitState: CounterState = {
count: 0,
}

export const createCounterStore = (
initState: CounterState = defaultInitState,
) => {
return createStore<CounterStore>()((set) => ({
...initState,
decrementCount: () => set((state) => ({ count: state.count - 1 })),
incrementCount: () => set((state) => ({ count: state.count + 1 })),
}))
}
```

```tsx
// components/counter-store-provider.tsx
// rest of code
// src/providers/counter-store-provider.tsx
'use client'

import { type ReactNode, createContext, useRef, useContext } from 'react'
import { type StoreApi, useStore } from 'zustand'

import {
type CounterStore,
createCounterStore,
initCounterStore,
} from 'stores/counter-store.ts'
} from '@/stores/counter-store'

// rest of code
export const CounterStoreContext = createContext<StoreApi<CounterStore> | null>(
null,
)

export interface CounterStoreProviderProps {
children: ReactNode
}

export const CounterStoreProvider = ({
children,
}: CounterStoreProviderProps) => {
const storeRef = useRef<CounterStore>()
const storeRef = useRef<StoreApi<CounterStore>>()
if (!storeRef.current) {
storeRef.current = createCounterStore(initCounterStore())
}
Expand All @@ -154,7 +225,17 @@ export const CounterStoreProvider = ({
)
}

// rest of code
export const useCounterStore = <T,>(
selector: (store: CounterStore) => T,
): T => {
const counterStoreContext = useContext(CounterStoreContext)

if (!counterStoreContext) {
throw new Error(`useCounterStore must be use within CounterStoreProvider`)
}

return useStore(counterStoreContext, selector)
}
```

### Using the store with different architectures
Expand All @@ -167,11 +248,13 @@ both architectures should be the same with slight differences related to each ar
#### Pages Router

```tsx
// components/pages/home-page.tsx
import { useCounterStore } from 'components/counter-store-provider.ts'
// src/components/pages/home-page.tsx
import { useCounterStore } from '@/providers/counter-store-provider.ts'

export const HomePage = () => {
const { count, incrementCount, decrementCount } = useCounterStore()
const { count, incrementCount, decrementCount } = useCounterStore(
(state) => state,
)

return (
<div>
Expand All @@ -189,10 +272,10 @@ export const HomePage = () => {
```

```tsx
// _app.tsx
// src/_app.tsx
import type { AppProps } from 'next/app'

import { CounterStoreProvider } from 'components/counter-store-provider.tsx'
import { CounterStoreProvider } from '@/providers/counter-store-provider.tsx'

export default function App({ Component, pageProps }: AppProps) {
return (
Expand All @@ -203,14 +286,23 @@ export default function App({ Component, pageProps }: AppProps) {
}
```

```tsx
// src/pages/index.tsx
import { HomePage } from '@/components/pages/home-page.tsx'

export default function Home() {
return <HomePage />
}
```

> **Note:** creating a store per route would require creating and sharing the store
> at page (route) component level. Try not to use this if you do not need to create
> a store per route.
```tsx
// pages/index.tsx
import { CounterStoreProvider } from 'components/counter-store-provider.tsx'
import { HomePage } from 'components/pages/home-page.tsx'
// src/pages/index.tsx
import { CounterStoreProvider } from '@/providers/counter-store-provider.tsx'
import { HomePage } from '@/components/pages/home-page.tsx'

export default function Home() {
return (
Expand All @@ -224,11 +316,15 @@ export default function Home() {
#### App Router

```tsx
// components/pages/home-page.tsx
import { useCounterStore } from 'components/counter-store-provider.ts'
// src/components/pages/home-page.tsx
'use client'

import { useCounterStore } from '@/providers/counter-store-provider'

export const HomePage = () => {
const { count, incrementCount, decrementCount } = useCounterStore()
const { count, incrementCount, decrementCount } = useCounterStore(
(state) => state,
)

return (
<div>
Expand All @@ -246,15 +342,41 @@ export const HomePage = () => {
```

```tsx
// app/layout.tsx
import { type ReactNode } from 'react'
// src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

export interface RootLayoutProps {
children: ReactNode
import { CounterStoreProvider } from '@/providers/counter-store-provider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={inter.className}>
<CounterStoreProvider>{children}</CounterStoreProvider>
</body>
</html>
)
}
```

```tsx
// src/app/page.tsx
import { HomePage } from '@/components/pages/home-page'

export default function RootLayout({ children }: RootLayoutProps) {
return <CounterStoreProvider>{children}</CounterStoreProvider>
export default function Home() {
return <HomePage />
}
```

Expand All @@ -263,9 +385,9 @@ export default function RootLayout({ children }: RootLayoutProps) {
> a store per route.
```tsx
// app/index.tsx
import { CounterStoreProvider } from 'components/counter-store-provider.tsx'
import { HomePage } from 'components/pages/home-page.tsx'
// src/app/page.tsx
import { CounterStoreProvider } from '@/providers/counter-store-provider'
import { HomePage } from '@/components/pages/home-page'

export default function Home() {
return (
Expand Down

0 comments on commit 8c1a1f0

Please sign in to comment.