@kopexa/use-controllable-state 0.0.0-canary-20250718183330
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 +72 -0
- package/dist/index.d.mts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +66 -0
- package/dist/index.mjs +40 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @kopexa/use-controllable-state
|
|
2
|
+
|
|
3
|
+
A React hook for building controlled and uncontrolled components, designed for clarity and reliability in the style of the Google API Design Guide.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`@kopexa/use-controllable-state` provides a robust pattern for managing state in React components that can be either controlled (via props) or uncontrolled (internal state). This hook is ideal for reusable UI components and libraries.
|
|
10
|
+
|
|
11
|
+
- **Author:** Kopexa (<https://kopexa.com>)
|
|
12
|
+
- **License:** MIT
|
|
13
|
+
- **Repository:** [kopexa-grc/sight](https://github.com/kopexa-grc/sight)
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Supports both controlled and uncontrolled usage
|
|
18
|
+
- Customizable change detection with `shouldUpdate`
|
|
19
|
+
- TypeScript support
|
|
20
|
+
- Lightweight and dependency-free (except React)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm add @kopexa/use-controllable-state
|
|
26
|
+
# or
|
|
27
|
+
yarn add @kopexa/use-controllable-state
|
|
28
|
+
# or
|
|
29
|
+
npm install @kopexa/use-controllable-state
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { useControllableState } from '@kopexa/use-controllable-state';
|
|
36
|
+
|
|
37
|
+
function MyComponent(props) {
|
|
38
|
+
const [value, setValue] = useControllableState({
|
|
39
|
+
value: props.value,
|
|
40
|
+
defaultValue: 0,
|
|
41
|
+
onChange: props.onChange,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return <input value={value} onChange={e => setValue(e.target.value)} />;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### `useControllableState<T>(props: UseControllableStateProps<T>): [T, Dispatch<SetStateAction<T>>]`
|
|
51
|
+
|
|
52
|
+
- **props**: See below for details.
|
|
53
|
+
- **Returns**: `[value, setValue]` — the current value and a setter function.
|
|
54
|
+
|
|
55
|
+
#### `UseControllableStateProps<T>`
|
|
56
|
+
- `value?`: Controlled value
|
|
57
|
+
- `defaultValue?`: Initial value for uncontrolled usage
|
|
58
|
+
- `onChange?`: Callback when value changes
|
|
59
|
+
- `shouldUpdate?`: Custom comparison function
|
|
60
|
+
|
|
61
|
+
## Best Practices
|
|
62
|
+
|
|
63
|
+
- Use for components that can be both controlled and uncontrolled.
|
|
64
|
+
- Provide clear documentation for consumers of your component.
|
|
65
|
+
|
|
66
|
+
## Why Kopexa?
|
|
67
|
+
|
|
68
|
+
Kopexa (<https://kopexa.com>) builds reliable, developer-friendly open source tools. This package is designed for clarity, stability, and best practices, inspired by the Google API Design Guide.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT © Kopexa
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if a component is controlled (receives its value from props) or uncontrolled (manages its own state).
|
|
3
|
+
* Returns a tuple: [isControlled, value].
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of the value.
|
|
6
|
+
* @param prop - The controlled value from props, or undefined if uncontrolled.
|
|
7
|
+
* @param state - The internal state value.
|
|
8
|
+
* @returns [boolean, T] - Whether the component is controlled, and the current value.
|
|
9
|
+
*/
|
|
10
|
+
declare function useControllableProp<T>(prop: T | undefined, state: T): [boolean, T];
|
|
11
|
+
/**
|
|
12
|
+
* Props for the useControllableState hook.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of the value.
|
|
15
|
+
* @property value - The controlled value (if provided).
|
|
16
|
+
* @property defaultValue - The initial value for uncontrolled usage.
|
|
17
|
+
* @property onChange - Callback fired when the value changes.
|
|
18
|
+
* @property shouldUpdate - Custom comparison function to determine if the value should update.
|
|
19
|
+
*/
|
|
20
|
+
interface UseControllableStateProps<T> {
|
|
21
|
+
value?: T;
|
|
22
|
+
defaultValue?: T | (() => T);
|
|
23
|
+
onChange?: (value: T) => void;
|
|
24
|
+
shouldUpdate?: (prev: T, next: T) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A React hook for building controlled or uncontrolled components.
|
|
28
|
+
*
|
|
29
|
+
* - If `value` is provided, the component is controlled and must be updated via `onChange`.
|
|
30
|
+
* - If `value` is not provided, the component manages its own state internally.
|
|
31
|
+
*
|
|
32
|
+
* Returns a tuple: [value, setValue].
|
|
33
|
+
*
|
|
34
|
+
* @template T - The type of the value.
|
|
35
|
+
* @param props - The props for controlling the state.
|
|
36
|
+
* @returns [T, Dispatch<SetStateAction<T>>] - The current value and a setter function.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const [value, setValue] = useControllableState({
|
|
40
|
+
* value: props.value,
|
|
41
|
+
* defaultValue: 0,
|
|
42
|
+
* onChange: props.onChange,
|
|
43
|
+
* });
|
|
44
|
+
*/
|
|
45
|
+
declare function useControllableState<T>(props: UseControllableStateProps<T>): [T, React.Dispatch<React.SetStateAction<T>>];
|
|
46
|
+
|
|
47
|
+
export { type UseControllableStateProps, useControllableProp, useControllableState };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if a component is controlled (receives its value from props) or uncontrolled (manages its own state).
|
|
3
|
+
* Returns a tuple: [isControlled, value].
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of the value.
|
|
6
|
+
* @param prop - The controlled value from props, or undefined if uncontrolled.
|
|
7
|
+
* @param state - The internal state value.
|
|
8
|
+
* @returns [boolean, T] - Whether the component is controlled, and the current value.
|
|
9
|
+
*/
|
|
10
|
+
declare function useControllableProp<T>(prop: T | undefined, state: T): [boolean, T];
|
|
11
|
+
/**
|
|
12
|
+
* Props for the useControllableState hook.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of the value.
|
|
15
|
+
* @property value - The controlled value (if provided).
|
|
16
|
+
* @property defaultValue - The initial value for uncontrolled usage.
|
|
17
|
+
* @property onChange - Callback fired when the value changes.
|
|
18
|
+
* @property shouldUpdate - Custom comparison function to determine if the value should update.
|
|
19
|
+
*/
|
|
20
|
+
interface UseControllableStateProps<T> {
|
|
21
|
+
value?: T;
|
|
22
|
+
defaultValue?: T | (() => T);
|
|
23
|
+
onChange?: (value: T) => void;
|
|
24
|
+
shouldUpdate?: (prev: T, next: T) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A React hook for building controlled or uncontrolled components.
|
|
28
|
+
*
|
|
29
|
+
* - If `value` is provided, the component is controlled and must be updated via `onChange`.
|
|
30
|
+
* - If `value` is not provided, the component manages its own state internally.
|
|
31
|
+
*
|
|
32
|
+
* Returns a tuple: [value, setValue].
|
|
33
|
+
*
|
|
34
|
+
* @template T - The type of the value.
|
|
35
|
+
* @param props - The props for controlling the state.
|
|
36
|
+
* @returns [T, Dispatch<SetStateAction<T>>] - The current value and a setter function.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const [value, setValue] = useControllableState({
|
|
40
|
+
* value: props.value,
|
|
41
|
+
* defaultValue: 0,
|
|
42
|
+
* onChange: props.onChange,
|
|
43
|
+
* });
|
|
44
|
+
*/
|
|
45
|
+
declare function useControllableState<T>(props: UseControllableStateProps<T>): [T, React.Dispatch<React.SetStateAction<T>>];
|
|
46
|
+
|
|
47
|
+
export { type UseControllableStateProps, useControllableProp, useControllableState };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
useControllableProp: () => useControllableProp,
|
|
24
|
+
useControllableState: () => useControllableState
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var import_use_callback_ref = require("@kopexa/use-callback-ref");
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
function useControllableProp(prop, state) {
|
|
30
|
+
const controlled = typeof prop !== "undefined";
|
|
31
|
+
const value = controlled ? prop : state;
|
|
32
|
+
return (0, import_react.useMemo)(() => [controlled, value], [controlled, value]);
|
|
33
|
+
}
|
|
34
|
+
function useControllableState(props) {
|
|
35
|
+
const {
|
|
36
|
+
value: valueProp,
|
|
37
|
+
defaultValue,
|
|
38
|
+
onChange,
|
|
39
|
+
shouldUpdate = (prev, next) => prev !== next
|
|
40
|
+
} = props;
|
|
41
|
+
const onChangeProp = (0, import_use_callback_ref.useCallbackRef)(onChange);
|
|
42
|
+
const shouldUpdateProp = (0, import_use_callback_ref.useCallbackRef)(shouldUpdate);
|
|
43
|
+
const [uncontrolledState, setUncontrolledState] = (0, import_react.useState)(defaultValue);
|
|
44
|
+
const controlled = valueProp !== void 0;
|
|
45
|
+
const value = controlled ? valueProp : uncontrolledState;
|
|
46
|
+
const setValue = (0, import_use_callback_ref.useCallbackRef)(
|
|
47
|
+
(next) => {
|
|
48
|
+
const setter = next;
|
|
49
|
+
const nextValue = typeof next === "function" ? setter(value) : next;
|
|
50
|
+
if (!shouldUpdateProp(value, nextValue)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!controlled) {
|
|
54
|
+
setUncontrolledState(nextValue);
|
|
55
|
+
}
|
|
56
|
+
onChangeProp(nextValue);
|
|
57
|
+
},
|
|
58
|
+
[controlled, onChangeProp, value, shouldUpdateProp]
|
|
59
|
+
);
|
|
60
|
+
return [value, setValue];
|
|
61
|
+
}
|
|
62
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
63
|
+
0 && (module.exports = {
|
|
64
|
+
useControllableProp,
|
|
65
|
+
useControllableState
|
|
66
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { useCallbackRef } from "@kopexa/use-callback-ref";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
function useControllableProp(prop, state) {
|
|
5
|
+
const controlled = typeof prop !== "undefined";
|
|
6
|
+
const value = controlled ? prop : state;
|
|
7
|
+
return useMemo(() => [controlled, value], [controlled, value]);
|
|
8
|
+
}
|
|
9
|
+
function useControllableState(props) {
|
|
10
|
+
const {
|
|
11
|
+
value: valueProp,
|
|
12
|
+
defaultValue,
|
|
13
|
+
onChange,
|
|
14
|
+
shouldUpdate = (prev, next) => prev !== next
|
|
15
|
+
} = props;
|
|
16
|
+
const onChangeProp = useCallbackRef(onChange);
|
|
17
|
+
const shouldUpdateProp = useCallbackRef(shouldUpdate);
|
|
18
|
+
const [uncontrolledState, setUncontrolledState] = useState(defaultValue);
|
|
19
|
+
const controlled = valueProp !== void 0;
|
|
20
|
+
const value = controlled ? valueProp : uncontrolledState;
|
|
21
|
+
const setValue = useCallbackRef(
|
|
22
|
+
(next) => {
|
|
23
|
+
const setter = next;
|
|
24
|
+
const nextValue = typeof next === "function" ? setter(value) : next;
|
|
25
|
+
if (!shouldUpdateProp(value, nextValue)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!controlled) {
|
|
29
|
+
setUncontrolledState(nextValue);
|
|
30
|
+
}
|
|
31
|
+
onChangeProp(nextValue);
|
|
32
|
+
},
|
|
33
|
+
[controlled, onChangeProp, value, shouldUpdateProp]
|
|
34
|
+
);
|
|
35
|
+
return [value, setValue];
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
useControllableProp,
|
|
39
|
+
useControllableState
|
|
40
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kopexa/use-controllable-state",
|
|
3
|
+
"version": "0.0.0-canary-20250718183330",
|
|
4
|
+
"description": "A hook to provide a controllable state",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"use-controllable-state"
|
|
7
|
+
],
|
|
8
|
+
"author": "Kopexa <hello@kopexa.com>",
|
|
9
|
+
"homepage": "https://kopexa.com",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/kopexa-grc/sight.git",
|
|
22
|
+
"directory": "packages/hooks/use-controllable-state"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/kopexa-grc/sight/issues"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=19.0.0-rc.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@kopexa/use-callback-ref": "0.0.0-canary-20250718183330"
|
|
32
|
+
},
|
|
33
|
+
"clean-package": "../../../clean-package.config.json",
|
|
34
|
+
"tsup": {
|
|
35
|
+
"clean": true,
|
|
36
|
+
"target": "es2019",
|
|
37
|
+
"format": [
|
|
38
|
+
"cjs",
|
|
39
|
+
"esm"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"module": "dist/index.mjs",
|
|
43
|
+
"types": "dist/index.d.ts",
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"import": "./dist/index.mjs",
|
|
48
|
+
"require": "./dist/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./package.json": "./package.json"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup src --dts",
|
|
54
|
+
"build:fast": "tsup src",
|
|
55
|
+
"dev": "pnpm build:fast --watch",
|
|
56
|
+
"clean": "rimraf dist .turbo",
|
|
57
|
+
"typecheck": "tsc --noEmit"
|
|
58
|
+
}
|
|
59
|
+
}
|