diff --git a/.changeset/great-wasps-juggle.md b/.changeset/great-wasps-juggle.md new file mode 100644 index 0000000..d3db314 --- /dev/null +++ b/.changeset/great-wasps-juggle.md @@ -0,0 +1,6 @@ +--- +"@pionjs/pion": minor +--- + +Improve the type definition of useState and useProperty adding better support for initial values +and return better defined state updaters. diff --git a/src/core.ts b/src/core.ts index 6fc3b5a..637f9f5 100644 --- a/src/core.ts +++ b/src/core.ts @@ -58,3 +58,5 @@ export { State } from "./state"; export type { Ref } from "./use-ref"; export type { Options as ComponentOptions } from "./component"; + +export type { StateUpdater } from "./use-state"; diff --git a/src/use-property.ts b/src/use-property.ts index 056bc62..d26f4bb 100644 --- a/src/use-property.ts +++ b/src/use-property.ts @@ -1,18 +1,27 @@ import { hook, Hook } from "./hook"; import { State } from "./state"; +import type { + InitialState, + NewState, + StateUpdater, + StateTuple, +} from "./use-state"; type Host = Element & { [key: string]: T }; -type NewState = T | ((previousState?: T) => T); -type StateUpdater = (value: NewState) => void; type ChangeEvent = { value: T; path: string; }; +export interface UseProperty { + (property: string): StateTuple; + (property: string, value?: InitialState): StateTuple; +} + const UPPER = /([A-Z])/gu; export const useProperty = hook( - class extends Hook<[string, T], [T, StateUpdater], Host> { + class extends Hook<[string, T], StateTuple, Host> { property: string; eventName: string; @@ -20,7 +29,7 @@ export const useProperty = hook( id: number, state: State>, property: string, - initialValue: NewState + initialValue: InitialState ) { super(id, state); @@ -46,7 +55,7 @@ export const useProperty = hook( this.updateProp(initialValue); } - update(ignored: string, ignored2: T): [T, StateUpdater] { + update(ignored: string, ignored2: T): StateTuple { return [this.state.host[this.property], this.updater]; } @@ -54,7 +63,7 @@ export const useProperty = hook( const previousValue = this.state.host[this.property]; if (typeof value === "function") { - const updaterFn = value as (previousState?: T) => T; + const updaterFn = value as (previousState: T) => T; value = updaterFn(previousValue); } @@ -80,16 +89,10 @@ export const useProperty = hook( return ev; } } -) as ( - property: string, - initialValue?: T -) => readonly [ - T extends (...args: any[]) => infer R ? R : T, - StateUpdater infer S ? S : T> -]; +) as UseProperty; export const lift = - (setter: (value: T) => void) => + (setter: StateUpdater) => (ev: CustomEvent>) => { ev.preventDefault(); setter(ev.detail.value); diff --git a/src/use-state.ts b/src/use-state.ts index cefcd24..cce467f 100644 --- a/src/use-state.ts +++ b/src/use-state.ts @@ -1,38 +1,46 @@ import { hook, Hook } from "./hook"; import { State } from "./state"; -type NewState = T | ((previousState?: T) => T); -type StateUpdater = (value: NewState) => void; +export type InitialState = T | (() => T); +export type NewState = T | ((previousState: T) => T); +export type StateUpdater = (value: NewState) => void; +export type StateTuple = readonly [T, StateUpdater]; + +export interface UseState { + (): StateTuple; + (value?: InitialState): StateTuple; +} /** * @function * @template {*} T * @param {T} [initialState] - Optional initial state - * @return {readonly [state: T, updaterFn: StateUpdater]} stateTuple - Tuple of current state and state updater function + * @return {StateTuple} stateTuple - Tuple of current state and state updater function */ const useState = hook( class extends Hook { - args!: readonly [T, StateUpdater]; + args!: StateTuple; - constructor(id: number, state: State, initialValue: T) { + constructor(id: number, state: State, initialValue: InitialState) { super(id, state); this.updater = this.updater.bind(this); if (typeof initialValue === "function") { - initialValue = initialValue(); + const initFn = initialValue as () => T; + initialValue = initFn(); } this.makeArgs(initialValue); } - update(): readonly [T, StateUpdater] { + update(): StateTuple { return this.args; } updater(value: NewState): void { const [previousValue] = this.args; if (typeof value === "function") { - const updaterFn = value as (previousState?: T) => T; + const updaterFn = value as (previousState: T) => T; value = updaterFn(previousValue); } @@ -45,14 +53,9 @@ const useState = hook( } makeArgs(value: T): void { - this.args = Object.freeze([value, this.updater] as const); + this.args = Object.freeze([value, this.updater]); } } -) as ( - initialValue?: T -) => readonly [ - T extends (...args: any[]) => infer R ? R : T, - StateUpdater infer S ? S : T> -]; +) as UseState; export { useState };