Skip to content
Open
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1d34a5e
initial commit
Henry8192 Jun 22, 2025
a8e70ec
update Zustand store docs naming conventions & creation
Henry8192 Jun 26, 2025
4598dc9
add zustand slicing and store usage docs.
Henry8192 Jun 26, 2025
8c8be4d
Apply suggestions from code review
Henry8192 Jun 27, 2025
22526a6
patch previous commit
Henry8192 Jun 27, 2025
2159be2
apply the rest of suggestions
Henry8192 Jun 28, 2025
f92f375
move state values section
Henry8192 Jun 28, 2025
56a46b5
address coderabbit review
Henry8192 Jun 28, 2025
678c4cf
Apply suggestions from code review
Henry8192 Jun 30, 2025
8f96f8e
remove extra spaces
Henry8192 Jun 30, 2025
f5f00b7
Merge branch 'main' into zustand-store-docs
junhaoliao Jun 30, 2025
f5822d6
Update examples
Henry8192 Jun 30, 2025
d02feb6
address comment
Henry8192 Jul 8, 2025
08e9a07
specify when to slice
Henry8192 Jul 9, 2025
19c7959
Apply suggestions from code review
Henry8192 Jul 10, 2025
3dbb7ad
Merge branch 'main' into zustand-store-docs
Henry8192 Aug 11, 2025
c97fa29
Merge branch 'main' into zustand-store-docs
Henry8192 Aug 22, 2025
da469a6
Merge branch 'main' into zustand-store-docs
junhaoliao Sep 2, 2025
eb38e38
Merge branch 'main' into zustand-store-docs
junhaoliao Sep 12, 2025
e5842c4
docs: Update Zustand store naming and structure guidelines for clarit…
junhaoliao Sep 13, 2025
31faf0a
Merge branch 'main' into zustand-store-docs
Henry8192 Sep 17, 2025
515406c
address review
Henry8192 Sep 19, 2025
edfbb8c
Merge branch 'main' into zustand-store-docs
Henry8192 Oct 27, 2025
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
148 changes: 148 additions & 0 deletions docs/src/dev-guide/coding-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,154 @@ To avoid including a state variable in a React Hook's dependency array, you can
the state variable with an additional `Ref` suffix. E.g., `logEventNumRef` is the reference variable
that corresponds to the `logEventNum` state variable.

# Zustand

## File Naming

When creating Zustand stores, we follow these naming conventions:

* Store files: `{name}Store.ts` (camelCase).
* Large stores: create `{name}Store/` folder with:
* `index.ts` - main store file that combines slices.
* `create{Name}{Feature}Slice.ts` - individual feature slices (e.g., `createQueryConfigSlice.ts`).

## Store structure

### Type definitions

Split store types into three interfaces:

* `{Name}Values` - state variables
* `{Name}Actions` - action functions
* `{Name}State` - union of values and actions

When store gets too large, we slice `{Name}State` to `{Name}{Feature}Slice`.
Then we unionize these slices to `{Name}State`.


```{code-block} ts
:caption: Example: Log export store types
:emphasize-lines: 1,5,9
interface LogExportValues {
exportProgress: Nullable<number>;
}

interface LogExportActions {
setExportProgress: (newProgress: Nullable<number>) => void;
}

type LogExportState = LogExportValues & LogExportActions;
```

### Default values

* Create an object for initial state values.
* Type with `{Name}Values` interface for validation.

```{code-block} ts
:caption: Example: Log export store default values
const LOG_EXPORT_STORE_DEFAULT: LogExportValues = {
exportProgress: null,
};
```

### Action naming

* `set{Property}` - simple state updates.
* `update{Property}` - complex logic, API calls, or multiple state updates.

```{code-block} ts
:caption: Example: Log export store actions
:emphasize-lines: 2,5
const useUserStore = create<UserState>((set, get) => ({
setName: (name) => {
set({name});
},
updateProfile: async (data) => {
set({isLoading: true});
const result = await api.updateProfile(data);
set({
profile: result,
isLoading: false
});
},
}));
```

## Feature-based slicing

When a Zustand store file becomes too large, we should slice it based on features. Avoid slicing by type (e.g., all
values / actions in one object) - it's a common anti-pattern.

## Store access patterns

### Inside store creation

Use `get()` and `set()` to access the store's own states:

```{code-block} ts
:caption: Example: View format store access - inside store slice creation
:emphasize-lines: 3,5,10
const createViewFormattingSlice: StateCreator<
ViewState, [], [], ViewFormattingSlice
> = (set, get) => ({
updateIsPrettified: (newIsPrettified: boolean) => {
const {isPrettified} = get();
if (newIsPrettified === isPrettified) {
return;
}
// ...
set({isPrettified: newIsPrettified});
// ...
},
}));
```

### Inside React components

#### State values

Choose access pattern based on usage:

*Reactive access* - when the value is used in JSX or hook dependency arrays:

```{code-block} ts
:caption: Example: Log export store value access - reactive
const exportProgress = useLogExportStore((state) => state.exportProgress);

// The progress should be printed when `exportProgress` updates.
useEffect(() => {
console.log(exportProgress);
}, [exportProgress]);
```

*Non-reactive access* - when the value should not trigger re-renders or hook re-runs:

```{code-block} ts
:caption: Example: Log export store value access - non-reactive
// The progress should be printed only once when the component mounts.
useEffect(() => {
const {exportProgress} = useLogExportStore.getState();
console.log(exportProgress);
}, []);
```

#### Actions

Actions usually do not change after initialization, so always access them non-reactively:

```{code-block} ts
:caption: Example: Log export store action access - non-reactive
const handleExportButtonClick = useCallback(() => {
const {exportLogs} = useLogExportStore.getState();
exportLogs();
}, []);
```

### Outside React components

Always use non-reactive access since reactive subscriptions do not work outside components.

[eslint-config-mjs]: https://github.com/y-scope/yscope-log-viewer/blob/main/eslint.config.mjs
[vite-worker-query-suffix]: https://vite.dev/guide/features.html#import-with-query-suffixes
[yscope-guidelines]: https://docs.yscope.com/dev-guide/contrib-guides-overview.html