This project adheres to YScope's contribution guidelines as well as the project-specific guidelines below.
When importing web worker files, use Vite's ?worker query suffix syntax:
import MainWorker from "../services/MainWorker.worker?worker";
const worker = new MainWorker();This special syntax tells Vite to transform the import as a web worker constructor. See Vite's web worker documentation for more details.
Name web worker files with the extension, .worker.ts. This is to:
- follow standard practices.
- allow eslint.config.mjs to ignore
.worker.tsfiles, suppressingeslint-plugin-import:import/defaulterrors caused by Vite's?workerimport syntax.
To differentiate variables that use different starting indexes (0 vs. 1), use the following naming convention:
- 0-based indexing variable names should end with the suffix
Idx. - 1-based indexing variable names should end with the suffix
Num.
Similarly, variables that represent a total number of items should be named with the prefix num.
Examples:
logEventNumfor a 1-based indexing variable.arrayIndexIdxfor a 0-based indexing variable.numEventsfor the total number of events.
To avoid including a state variable in a React Hook's dependency array, you can use a reference
(mirror) to hold the current value of the state variable. The reference should use the same name as
the state variable with an additional Ref suffix. E.g., logEventNumRef is the reference variable
that corresponds to the logEventNum state variable.
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).
Split store types into three interfaces:
{Name}Values- state variables{Name}Actions- action functions{Name}Stateor{Name}Slice- combined type
: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;
- Create an object for initial state values.
- Type with
{Name}Valuesinterface for validation.
:caption: Example: Log export store default values
const LOG_EXPORT_STORE_DEFAULT: LogExportValues = {
exportProgress: null,
};
set{Property}- simple state updates.update{Property}- complex logic, API calls, or multiple state updates.
: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
});
},
}));
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.
Use get() and set() to access the store's own states:
:caption: Example: Log export store access - inside store creation
const useLogExportStore = create<LogExportState>((get, set) => ({
exportLogs: () => {
// ...
const {exportProgress} = get();
set({exportProgress: EXPORT_LOGS_PROGRESS_VALUE_MIN});
},
}));
Choose access pattern based on usage:
Reactive access - when the value is used in JSX or hook dependency arrays:
: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:
: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 usually do not change after initialization, so always access them non-reactively:
:caption: Example: Log export store action access - non-reactive
const handleExportButtonClick = useCallback(() => {
const {exportLogs} = useLogExportStore.getState();
exportLogs();
}, []);
Always use non-reactive access since reactive subscriptions do not work outside components.