better-preact-use-state 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ A utility that improves Preact's `useState` to be safer and more convenient. Pro
4
4
 
5
5
  ## Overview
6
6
 
7
- Preact's `useState` can cause unintended behavior when you want to hold a function as a state. `better-preact-use-state` solves this problem and is a utility designed for safer state management. It is designed as a drop-in replacement for Preact's `useState` hook, with added functionality. `better-preact-use-state` also provides a simplified hook, `useBoolState`, specifically for boolean state.
7
+ Preact's `useState` hands back a single setter that accepts either the next value or an updater function, so the two cannot be told apart by type. `better-preact-use-state` is a utility designed for safer state management: its `setState` accepts only the next value, and it also hands back `updateState` and `resetState`. It is designed as a drop-in replacement for Preact's `useState` hook, with added functionality. `better-preact-use-state` also provides a simplified hook, `useBoolState`, specifically for boolean state.
8
8
 
9
9
  ## Installation
10
10
 
@@ -30,27 +30,34 @@ pnpm add better-preact-use-state
30
30
 
31
31
  ```tsx
32
32
  import { useState } from 'better-preact-use-state';
33
+ import type * as Preact from 'preact';
34
+ import { useCallback } from 'preact/hooks';
33
35
 
34
- const MyComponent: FunctionalComponent = () => {
35
- const [name, setName] = useState('John Doe');
36
+ export const MyComponent = (): Preact.JSX.Element => {
37
+ const [userName, setUserName] = useState('John Doe');
36
38
 
37
- const [count, setCount, { updateState: updateCount }] = useState(0);
39
+ const [count, , { updateState: updateCount }] = useState(0);
38
40
 
39
- const onNameChange = useCallback((event) => {
40
- setName(event.target.value);
41
- }, []);
41
+ const onNameInput: Preact.InputEventHandler<HTMLInputElement> = useCallback(
42
+ (ev) => {
43
+ setUserName(ev.currentTarget.value);
44
+ },
45
+ [setUserName],
46
+ );
42
47
 
43
48
  const incrementCount = useCallback(() => {
44
49
  updateCount((x) => x + 1);
45
- }, []);
50
+ }, [updateCount]);
46
51
 
47
52
  return (
48
53
  <div>
49
54
  <p>{`Count: ${count}`}</p>
50
- <button onClick={incrementCount}>{'Increment'}</button>
55
+ <button type={'button'} onClick={incrementCount}>
56
+ {'Increment'}
57
+ </button>
51
58
 
52
- <p>{`Name: ${name}`}</p>
53
- <input type="text" value={name} onChange={onNameChange} />
59
+ <p>{`Name: ${userName}`}</p>
60
+ <input type={'text'} value={userName} onInput={onNameInput} />
54
61
  </div>
55
62
  );
56
63
  };
@@ -62,18 +69,25 @@ const MyComponent: FunctionalComponent = () => {
62
69
 
63
70
  ```tsx
64
71
  import { useBoolState } from 'better-preact-use-state';
72
+ import type * as Preact from 'preact';
65
73
 
66
- const MyComponent = () => {
67
- const [isOpen, { setTrue: open, setFalse: close, toggleState }] =
74
+ export const MyComponent = (): Preact.JSX.Element => {
75
+ const [isOpen, { setTrue: openPanel, setFalse: closePanel, toggleState }] =
68
76
  useBoolState(false);
69
77
 
70
78
  return (
71
79
  <div>
72
80
  <p>{`Is Open: ${isOpen ? 'Yes' : 'No'}`}</p>
73
- <button onClick={open}>{'Open'}</button>
74
- <button onClick={close}>{'Close'}</button>
75
- <button onClick={toggleState}>{'Toggle'}</button>
81
+ <button type={'button'} onClick={openPanel}>
82
+ {'Open'}
83
+ </button>
84
+ <button type={'button'} onClick={closePanel}>
85
+ {'Close'}
86
+ </button>
76
87
  {/* Toggles the boolean value */}
88
+ <button type={'button'} onClick={toggleState}>
89
+ {'Toggle'}
90
+ </button>
77
91
  </div>
78
92
  );
79
93
  };
@@ -83,8 +97,8 @@ const MyComponent = () => {
83
97
 
84
98
  ### `useState`
85
99
 
86
- ```typescript
87
- const [state, setState, { updateState, resetState }] = useState<T>(initialState: T);
100
+ ```ts
101
+ const [state, setState, { updateState, resetState }] = useState(initialState);
88
102
  ```
89
103
 
90
104
  - `state`: The current state.
@@ -94,24 +108,27 @@ const [state, setState, { updateState, resetState }] = useState<T>(initialState:
94
108
 
95
109
  #### `updateState`
96
110
 
97
- ```typescript
98
- updateState(updateFn: (v: T) => T): void;
111
+ ```ts
112
+ updateState: (updateFn: (v: T) => T) => void;
99
113
  ```
100
114
 
101
115
  Updates the state by passing a function `updateFn` that takes the current state as an argument and returns the new state.
102
116
 
103
117
  #### `resetState`
104
118
 
105
- ```typescript
106
- resetState(): void;
119
+ ```ts
120
+ resetState: () => void;
107
121
  ```
108
122
 
109
- Resets the state to the initial value.
123
+ Resets the state to the value it was created with: the first render's `initialState`, as `useState` reads it, whatever is passed later. Its identity never changes.
110
124
 
111
125
  ### `useBoolState`
112
126
 
113
- ```typescript
114
- const [state, { setState, setTrue, setFalse, resetState, toggleState, updateState }] = useBoolState(initialState: boolean);
127
+ ```ts
128
+ const [
129
+ state,
130
+ { setState, setTrue, setFalse, resetState, toggleState, updateState },
131
+ ] = useBoolState(initialState);
115
132
  ```
116
133
 
117
134
  - `state`: The current boolean value.
@@ -124,67 +141,66 @@ const [state, { setState, setTrue, setFalse, resetState, toggleState, updateStat
124
141
 
125
142
  #### `setState`
126
143
 
127
- ```typescript
128
- setState(next: boolean): void;
144
+ ```ts
145
+ setState: (next: boolean) => void;
129
146
  ```
130
147
 
131
148
  Sets the state to the provided boolean value.
132
149
 
133
150
  #### `setTrue`
134
151
 
135
- ```typescript
136
- setTrue(): void;
152
+ ```ts
153
+ setTrue: () => void;
137
154
  ```
138
155
 
139
156
  Sets the state to `true`.
140
157
 
141
158
  #### `setFalse`
142
159
 
143
- ```typescript
144
- setFalse(): void;
160
+ ```ts
161
+ setFalse: () => void;
145
162
  ```
146
163
 
147
164
  Sets the state to `false`.
148
165
 
149
166
  #### `resetState`
150
167
 
151
- ```typescript
152
- resetState(): void;
168
+ ```ts
169
+ resetState: () => void;
153
170
  ```
154
171
 
155
- Resets the state to the initial value.
172
+ Resets the state to the value it was created with: the first render's `initialState`, as `useState` reads it, whatever is passed later. Its identity never changes.
156
173
 
157
174
  #### `toggleState`
158
175
 
159
- ```typescript
160
- toggleState(): void;
176
+ ```ts
177
+ toggleState: () => void;
161
178
  ```
162
179
 
163
180
  Toggles the current boolean value.
164
181
 
165
182
  #### `updateState`
166
183
 
167
- ```typescript
168
- updateState(updateFn: (v: boolean) => boolean): void;
184
+ ```ts
185
+ updateState: (updateFn: (v: boolean) => boolean) => void;
169
186
  ```
170
187
 
171
188
  Updates the state by passing a function `updateFn` that takes the current state as an argument and returns the new state. This is useful for updates that depend on the current state.
172
189
 
173
190
  ## Benefits of this library
174
191
 
175
- With Preact's standard `useState`, attempting to store a function as state can lead to unexpected behavior (the function being interpreted as an updater function). `better-preact-use-state` solves this issue and, by providing `updateState`, allows functions to be safely held as state. Also, `resetState` lets you easily return the state to its initial value. `useBoolState` simplifies boolean state management in Preact, offering several advantages over directly using useState for booleans.
176
-
177
- ### Example Use Case
192
+ With Preact's standard `useState`, `setState` accepts either the next value or an updater function, and nothing in the types tells the two apart. `better-preact-use-state` separates them: `setState` is typed to accept only the next value, and an update that depends on the current value goes through `updateState` explicitly. `resetState` lets you easily return the state to its initial value. `useBoolState` simplifies boolean state management in Preact, offering several advantages over directly using `useState` for booleans.
178
193
 
179
- Useful when you want to hold a function as state.
194
+ ### Holding a function as state
180
195
 
181
- ```typescript
182
- const [fn, setFn, { updateState: updateFn }] = useState(initialFn); // Holds a function as state
196
+ The initial value and the argument of `setState` are passed to Preact's `useState` as they are, so a function given to either is still called as a lazy initializer or an updater, just as with Preact's own hook. To hold a function as state, wrap it in an object:
183
197
 
184
- // In Preact's standard useState, `setFn(nextFn)` interprets nextFn as an update function.
198
+ ```ts
199
+ const [{ fn }, setFn] = useState({ fn: initialFn });
185
200
 
186
- // If you want to update the state with an update function, you can use updateState instead.
187
- updateFn((currentFn) => nextFn);
201
+ const onReplace = useCallback((): void => {
202
+ setFn({ fn: nextFn });
203
+ }, [setFn, nextFn]);
188
204
  ```
189
205
 
190
206
  ## License
@@ -1 +1 @@
1
- {"version":3,"file":"use-state.d.mts","sourceRoot":"","sources":["../src/use-state.mts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,GAAI,CAAC,gBACV,CAAC,KACd,SAAS,CACV,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,EAC3B,OAAO,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC,CAaH,CAAC"}
1
+ {"version":3,"file":"use-state.d.mts","sourceRoot":"","sources":["../src/use-state.mts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,GAAI,CAAC,gBACV,CAAC,KACd,SAAS,CACV,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,EAC3B,OAAO,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC,CAkBH,CAAC"}
@@ -1,9 +1,13 @@
1
1
  import * as Preact from 'preact/hooks';
2
2
  export const useState = (initialState) => {
3
3
  const [state, setState] = Preact.useState(initialState);
4
+
5
+
6
+
7
+ const [initial] = Preact.useState(initialState);
4
8
  const resetState = Preact.useCallback(() => {
5
- setState(() => initialState);
6
- }, [initialState]);
9
+ setState(() => initial);
10
+ }, [initial]);
7
11
  const updateState = Preact.useCallback((updateFn) => {
8
12
  setState(updateFn);
9
13
  }, []);
@@ -1 +1 @@
1
- {"version":3,"file":"use-state.mjs","sourceRoot":"","sources":["../src/use-state.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AAEvC,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,YAAe,EAQf,EAAE;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAI,YAAY,CAAC,CAAC;IAE3D,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;QACzC,QAAQ,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;IAC/B,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAqB,EAAE,EAAE;QAC/D,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;AACxD,CAAC,CAAC"}
1
+ {"version":3,"file":"use-state.mjs","sourceRoot":"","sources":["../src/use-state.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AAEvC,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,YAAe,EAQf,EAAE;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAI,YAAY,CAAC,CAAC;IAE3D,6EAA6E;IAC7E,0EAA0E;IAC1E,uEAAuE;IACvE,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAI,YAAY,CAAC,CAAC;IAEnD,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;QACzC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAqB,EAAE,EAAE;QAC/D,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;AACxD,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-preact-use-state",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "description": "useState that also hands back the setters you would otherwise write by hand: updateState, resetState, and setTrue / setFalse / toggleState for booleans.",
6
6
  "keywords": [
@@ -33,24 +33,27 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "devDependencies": {
36
- "eslint": "10.9.1",
37
- "eslint-config-typed": "5.11.2",
38
- "eslint-plugin-ts-data-forge": "0.7.0",
39
- "eslint-plugin-ts-type-forge": "0.8.1",
40
- "npm-run-all2": "9.0.3",
36
+ "@testing-library/preact": "3.2.4",
37
+ "@vitest/browser-playwright": "5.0.5",
38
+ "eslint": "10.13.0",
39
+ "eslint-config-typed": "7.14.0",
40
+ "eslint-plugin-ts-data-forge": "0.13.1",
41
+ "eslint-plugin-ts-type-forge": "0.8.26",
42
+ "npm-run-all2": "9.0.4",
43
+ "playwright": "1.64.1",
41
44
  "preact": "10.29.8",
42
- "prettier": "3.9.6",
45
+ "prettier": "3.9.10",
43
46
  "prettier-plugin-organize-imports": "4.3.0",
44
47
  "prettier-plugin-packagejson": "3.0.2",
45
- "ts-data-forge": "14.6.3",
46
- "ts-repo-utils": "10.6.0",
47
- "tsx": "4.23.12"
48
+ "ts-data-forge": "15.0.1",
49
+ "ts-repo-utils": "11.0.1",
50
+ "tsx": "4.23.15"
48
51
  },
49
52
  "peerDependencies": {
50
53
  "preact": ">=10.0.0"
51
54
  },
52
55
  "engines": {
53
- "node": ">=22.22.2",
56
+ "node": ">=22.23.4",
54
57
  "pnpm": ">=9.0.0"
55
58
  },
56
59
  "publishConfig": {
@@ -59,18 +62,24 @@
59
62
  },
60
63
  "scripts": {
61
64
  "build": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts",
62
- "build:min": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts --skip-check",
63
65
  "check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
64
- "fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
65
- "fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
66
- "fmt:full": "pnpm run z:prettier .",
67
- "gi": "run-s gi:src fmt",
68
- "gi:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/gen-index-ts.mts ./src --index-ext .mts --export-ext .mjs --target-ext .mts",
69
- "lint": "eslint .",
70
- "lint:fix": "eslint . --fix",
71
- "tsc": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
72
- "tscw": "node ../../node_modules/typescript-native/bin/tsc --noEmit --watch -p ./tsconfig.json",
73
- "type-check": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
74
- "z:prettier": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --ignore-path ../../.gitignore --ignore-unknown --no-error-on-unmatched-pattern --write"
66
+ "check:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . && eslint .",
67
+ "check:test": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest:node run",
68
+ "check:test:browser": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest --project='Browser' run",
69
+ "check:types": "node ../../tools/scripts/cmd/assert-deps-built.mts && node ../../node_modules/typescript-native/bin/tsc --noEmit",
70
+ "doc": "run-s gen:readme",
71
+ "fix:fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
72
+ "fix:fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
73
+ "fix:fmt:full": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../tools/scripts/cmd/format-package.mts",
74
+ "fix:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . --fix && eslint . --fix",
75
+ "fmt": "pnpm run fix:fmt",
76
+ "gen": "run-s gen:index gen:readme",
77
+ "gen:index": "run-s gen:index:src fix:fmt",
78
+ "gen:index:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/gen-index-ts.mts ./src --index-ext .mts --export-ext .mjs --target-ext .mts",
79
+ "gen:readme": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
80
+ "test": "pnpm run check:test",
81
+ "watch:types": "node ../../node_modules/typescript-native/bin/tsc --noEmit --watch -p ./tsconfig.json",
82
+ "z:vitest": "vitest --config ./configs/vitest.config.mts",
83
+ "z:vitest:node": "pnpm run z:vitest --project='Node.js'"
75
84
  }
76
85
  }
package/src/use-state.mts CHANGED
@@ -12,9 +12,14 @@ export const useState = <T,>(
12
12
  ] => {
13
13
  const [state, setState] = Preact.useState<T>(initialState);
14
14
 
15
+ // The value the state was created with, read once as `useState` reads it, so
16
+ // that `resetState` neither follows a later argument nor changes identity
17
+ // when one is passed (an object literal is a new one on every render).
18
+ const [initial] = Preact.useState<T>(initialState);
19
+
15
20
  const resetState = Preact.useCallback(() => {
16
- setState(() => initialState);
17
- }, [initialState]);
21
+ setState(() => initial);
22
+ }, [initial]);
18
23
 
19
24
  const updateState = Preact.useCallback((updateFn: (v: T) => T) => {
20
25
  setState(updateFn);