docs: general docs improvements (#3102)

* feat: general docs improvements

* Update docs/guides/testing.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Danilo Britto 2025-04-29 11:15:42 -05:00 committed by GitHub
parent bd58db0faf
commit 48985a4cc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 285 additions and 7 deletions

View File

@ -288,7 +288,7 @@ export default defineConfig((configEnv) =>
)
```
### Testing components
### Testing Components
In the next examples we are going to use `useCounterStore`
@ -425,9 +425,7 @@ describe('Counter', () => {
expect(await screen.findByText(/^1$/)).toBeInTheDocument()
await act(async () => {
await user.click(await screen.findByRole('button', { name: /one up/i }))
})
await user.click(await screen.findByRole('button', { name: /one up/i }))
expect(await screen.findByText(/^2$/)).toBeInTheDocument()
})
@ -495,9 +493,7 @@ describe('CounterWithContext', () => {
expect(await screen.findByText(/^1$/)).toBeInTheDocument()
await act(async () => {
await user.click(await screen.findByRole('button', { name: /one up/i }))
})
await user.click(await screen.findByRole('button', { name: /one up/i }))
expect(await screen.findByText(/^2$/)).toBeInTheDocument()
})
@ -516,6 +512,229 @@ const renderCounterWithContext = () => {
- Jest Demo: https://stackblitz.com/edit/jest-zustand
- Vitest Demo: https://stackblitz.com/edit/vitest-zustand
### Testing Stores
In the next examples we are going to use `useCounterStore`
> **Note**: all of these examples are written using TypeScript.
```ts
// shared/counter-store-creator.ts
import { type StateCreator } from 'zustand'
export type CounterStore = {
count: number
inc: () => void
}
export const counterStoreCreator: StateCreator<CounterStore> = (set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
})
```
```ts
// stores/use-counter-store.ts
import { create } from 'zustand'
import {
type CounterStore,
counterStoreCreator,
} from '../shared/counter-store-creator'
export const useCounterStore = create<CounterStore>()(counterStoreCreator)
```
```tsx
// contexts/use-counter-store-context.tsx
import { type ReactNode, createContext, useContext, useRef } from 'react'
import { createStore } from 'zustand'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
import {
type CounterStore,
counterStoreCreator,
} from '../shared/counter-store-creator'
export const createCounterStore = () => {
return createStore<CounterStore>(counterStoreCreator)
}
export type CounterStoreApi = ReturnType<typeof createCounterStore>
export const CounterStoreContext = createContext<CounterStoreApi | undefined>(
undefined,
)
export interface CounterStoreProviderProps {
children: ReactNode
}
export const CounterStoreProvider = ({
children,
}: CounterStoreProviderProps) => {
const counterStoreRef = useRef<CounterStoreApi>(null)
if (!counterStoreRef.current) {
counterStoreRef.current = createCounterStore()
}
return (
<CounterStoreContext.Provider value={counterStoreRef.current}>
{children}
</CounterStoreContext.Provider>
)
}
export type UseCounterStoreContextSelector<T> = (store: CounterStore) => T
export const useCounterStoreContext = <T,>(
selector: UseCounterStoreContextSelector<T>,
): T => {
const counterStoreContext = useContext(CounterStoreContext)
if (counterStoreContext === undefined) {
throw new Error(
'useCounterStoreContext must be used within CounterStoreProvider',
)
}
return useStoreWithEqualityFn(counterStoreContext, selector, shallow)
}
```
```tsx
// components/counter/counter.tsx
import { useCounterStore } from '../../stores/use-counter-store'
export function Counter() {
const { count, inc } = useCounterStore()
return (
<div>
<h2>Counter Store</h2>
<h4>{count}</h4>
<button onClick={inc}>One Up</button>
</div>
)
}
```
```ts
// components/counter/index.ts
export * from './counter'
```
```tsx
// components/counter/counter.test.tsx
import { act, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Counter, useCounterStore } from '../../../stores/use-counter-store.ts'
describe('Counter', () => {
test('should render with initial state of 1', async () => {
renderCounter()
expect(useCounterStore.getState().count).toBe(1)
})
test('should increase count by clicking a button', async () => {
const user = userEvent.setup()
renderCounter()
expect(useCounterStore.getState().count).toBe(1)
await user.click(await screen.findByRole('button', { name: /one up/i }))
expect(useCounterStore.getState().count).toBe(2)
})
})
const renderCounter = () => {
return render(<Counter />)
}
```
```tsx
// components/counter-with-context/counter-with-context.tsx
import {
CounterStoreProvider,
useCounterStoreContext,
} from '../../contexts/use-counter-store-context'
const Counter = () => {
const { count, inc } = useCounterStoreContext((state) => state)
return (
<div>
<h2>Counter Store Context</h2>
<h4>{count}</h4>
<button onClick={inc}>One Up</button>
</div>
)
}
export const CounterWithContext = () => {
return (
<CounterStoreProvider>
<Counter />
</CounterStoreProvider>
)
}
```
```tsx
// components/counter-with-context/index.ts
export * from './counter-with-context'
```
```tsx
// components/counter-with-context/counter-with-context.test.tsx
import { act, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { CounterStoreContext } from '../../../contexts/use-counter-store-context'
import { counterStoreCreator } from '../../../shared/counter-store-creator'
describe('CounterWithContext', () => {
test('should render with initial state of 1', async () => {
const counterStore = counterStoreCreator()
renderCounterWithContext(counterStore)
expect(counterStore.getState().count).toBe(1)
expect(
await screen.findByRole('button', { name: /one up/i }),
).toBeInTheDocument()
})
test('should increase count by clicking a button', async () => {
const user = userEvent.setup()
const counterStore = counterStoreCreator()
renderCounterWithContext(counterStore)
expect(counterStore.getState().count).toBe(1)
await user.click(await screen.findByRole('button', { name: /one up/i }))
expect(counterStore.getState().count).toBe(2)
})
})
const renderCounterWithContext = (store) => {
return render(<CounterWithContext />, {
wrapper: ({ children }) => (
<CounterStoreContext.Provider value={store}>
{children}
</CounterStoreContext.Provider>
),
})
}
```
## References
- **React Testing Library**: [React Testing Library (RTL)](https://testing-library.com/docs/react-testing-library/intro)

51
docs/llms.txt Normal file
View File

@ -0,0 +1,51 @@
# Zustand
Zustand is a fast, minimal state management library for React and JavaScript. It provides a simple API without context providers or boilerplate.
## Docs
- [Official Docs](https://zustand.docs.pmnd.rs): Complete reference for Zustand.
- [Getting Started](https://zustand.docs.pmnd.rs/getting-started/introduction): Learn the basics and create your first store.
## Core API
- [createStore](https://zustand.docs.pmnd.rs/apis/create-store): Create a standalone store without React.
- [shallow](https://zustand.docs.pmnd.rs/apis/shallow): Utility for shallow comparison of `objects`.
## Middlewares
- [combine](https://zustand.docs.pmnd.rs/middlewares/combine): Combine multiple slices into a single store.
- [devtools](https://zustand.docs.pmnd.rs/middlewares/devtools): Debug state changes using Redux DevTools.
- [immer](https://zustand.docs.pmnd.rs/middlewares/immer): Use `Immer` for immutable state updates.
- [persist](https://zustand.docs.pmnd.rs/middlewares/persist): Persist state to `localStorage` or other storage engines.
- [redux](https://zustand.docs.pmnd.rs/middlewares/redux): Use a reducer-style setup similar to `Redux`.
- [subscribeWithSelector](https://zustand.docs.pmnd.rs/middlewares/subscribe-with-selector): Subscribe to specific slices with selector support.
## React Bindings
- [create](https://zustand.docs.pmnd.rs/apis/create): Bind a store to React for use with hooks.
- [createWithEqualityFn](https://zustand.docs.pmnd.rs/apis/create-with-equality-fn): Like `create`, but allows custom equality functions.
## React Hooks
- [useStore](https://zustand.docs.pmnd.rs/hooks/use-store): Access and subscribe to store state.
- [useStoreWithEqualityFn](https://zustand.docs.pmnd.rs/hooks/use-store-with-equality-fn): Same as `useStore`, but with custom equality checks.
- [useShallow](https://zustand.docs.pmnd.rs/hooks/use-shallow): Use selectors with shallow equality comparison.
## Migration Guides
- [Migrate to v4](https://zustand.docs.pmnd.rs/migrations/migrating-to-v4): How to upgrade from Zustand v3.
- [Migrate to v5](https://zustand.docs.pmnd.rs/migrations/migrating-to-v5): How to upgrade from Zustand v4.
## Tutorials
- [Tic-Tac-Toe Guide](https://zustand.docs.pmnd.rs/guides/tutorial-tic-tac-toe): Build a game using Zustand state.
## More
- [Third-party Libraries](https://zustand.docs.pmnd.rs/integrations/third-party-libraries): Integrations with third-party libraries.
- [Zustand vs Other Libraries](https://zustand.docs.pmnd.rs/getting-started/comparison): Comparison with other state management libraries.
- [TypeScript](https://zustand.docs.pmnd.rs/guides/typescript): Guide to using Zustand with TypeScript.
- [Testing](https://zustand.docs.pmnd.rs/guides/testing): Best practices for testing Zustand stores.
- [Next.js](https://zustand.docs.pmnd.rs/guides/nextjs): Using Zustand with Next.js for state management.
- [Slices Pattern](https://zustand.docs.pmnd.rs/guides/slices-pattern): Organizing state into slices for scalability and maintainability.

View File

@ -9,6 +9,10 @@ nav: 205
`devtools` middleware lets you use [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools)
without Redux. Read more about the benefits of using [Redux DevTools for debugging](https://redux.js.org/style-guide/#use-the-redux-devtools-extension-for-debugging).
> [!IMPORTANT]
> In order to use `devtools` from `zustand/middleware` you need to install
> `@redux-devtools/extension` library.
```js
const nextStateCreatorFn = devtools(stateCreatorFn, devtoolsOptions)
```

View File

@ -8,6 +8,10 @@ nav: 206
`immer` middleware lets you perform immutable updates.
> [!IMPORTANT]
> In order to use `immer` from `zustand/middleware/immer` you need to install
> `immer` library.
```js
const nextStateCreatorFn = immer(stateCreatorFn)
```