Slice Factory
Location: front/src/model/common/lib/slice-factory.ts
Purpose
Factory for generating Zustand slices for form input fields. Reduces boilerplate (value, setter, getErrorMessages, getIsValid).
Key names can be input in kebab-case. Matching directory names ensures naming consistency.
Design Decisions
Why a factory?
Each slice was repeating the same structure:
ts
// Before: 26 lines/slice
export type TitleSlice = {
title: string
setTitle: (title: string) => void
getTitleErrorMessages: (phase: ValidationPhase) => string[]
getTitleIsValid: (phase: ValidationPhase) => boolean
}
export const createTitleSlice = (initial) => (set, get) => ({
title: initial,
setTitle: (v) => set({ title: v }),
getTitleErrorMessages: (phase) => getValidationErrorMessage({...}),
getTitleIsValid: (phase) => ... .length === 0,
})
// After: 4 lines/slice
export type TitleSlice = InputSliceShape<'title', string>
export const createTitleSlice = createInputSlice('title', titleValidation)
Type-safe key generation
InputSliceShape<K, T> auto-generates method names from key:
- •
'title'→title,setTitle,getTitleErrorMessages,getTitleIsValid - •Type-safe via TypeScript template literal types
Kebab-case support
Input keys in kebab-case are auto-converted to camelCase:
- •
'notify-subscribers'→notifySubscribers,setNotifySubscribers, ... - •Matches directory naming convention
Validation optional
Validation function is optional:
- •With:
createInputSlice('title', titleValidation)→ errors from validation result - •Without:
createInputSlice('category')→ errors always empty, IsValid always true
Use for enum fields or always-valid fields.
When NOT to use
Cases the factory doesn't cover:
- •Cross-field dependency:
passwordmust matchconfirmPassword - •Custom methods:
getPasswordStrength()or similar - •Conditional validation: validation rules change based on state
→ Write slice manually, or factory + spread composition
ts
// Manual example
export const createPasswordSlice = (initial) => (set, get) => ({
password: initial,
setPassword: (v) => set({ password: v }),
getPasswordErrorMessages: (phase) => {
const base = validateWithStandardSchema(...)
if (get().password !== get().confirmPassword) {
base.push({ key: 'password', input: v, message: 'Passwords must match' })
}
return base
},
...
})
// Factory + additional methods
const baseSlice = createInputSlice('password', passwordValidation)
export const createPasswordSlice = (initial) => (...args) => ({
...baseSlice(initial)(...args),
getPasswordStrength: () => calculateStrength(args[1]().password),
})
Key Files
- •
front/src/model/common/lib/slice-factory.ts- Factory implementation - •
front/src/model/common/lib/slice-factory.test.ts- Tests - •
front/src/common/lib/string.ts- kebabToCamelCase, capitalize - •
front/src/model/article/detail/form/inputs/*/slice.ts- Usage examples