react-state-monad 0.0.1
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 +248 -0
- package/dist/index.cjs +29 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/package.json +37 -0
- package/src/hooks/types.ts +12 -0
- package/src/hooks/useElementState.ts +26 -0
- package/src/hooks/useEmptyState.ts +13 -0
- package/src/hooks/useFieldState.ts +21 -0
- package/src/hooks/useRemapArray.ts +33 -0
- package/src/hooks/useStateObject.ts +15 -0
- package/src/implementations/emptyState.ts +43 -0
- package/src/implementations/validState.ts +60 -0
- package/src/index.ts +3 -0
- package/src/stateObject.ts +71 -0
- package/tsconfig.json +20 -0
package/README.md
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
# React State Monad
|
2
|
+
|
3
|
+
A set of hooks to manage/transform/filter states with monads in React.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
`react-state-monad` provides a set of monadic state management utilities, specifically designed to work seamlessly with
|
8
|
+
React's state hooks. It allows you to manage, transform, and filter states in a functional and declarative way using
|
9
|
+
monads like `Maybe` and `Option`.
|
10
|
+
|
11
|
+
This library leverages the power of monads to encapsulate state changes, enabling a cleaner, more predictable way to
|
12
|
+
handle various state conditions in React.
|
13
|
+
|
14
|
+
## Features
|
15
|
+
|
16
|
+
- Manage state using monads like `Maybe` and `Option`.
|
17
|
+
- Simplify handling of undefined or null values in state.
|
18
|
+
- Leverage functional programming patterns in React state management.
|
19
|
+
- Support for TypeScript with full type definitions.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
You can install `react-state-monad` using npm or yarn:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
npm install react-state-monad
|
27
|
+
```
|
28
|
+
|
29
|
+
or
|
30
|
+
|
31
|
+
```bash
|
32
|
+
yarn add react-state-monad
|
33
|
+
```
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Here's an example of how you can use the hooks in your React components:
|
38
|
+
|
39
|
+
### `useStateObject<T>`
|
40
|
+
|
41
|
+
This hook initializes a StateObject with the provided initial value. It uses React's useState hook to manage the
|
42
|
+
internal state and returns a StateObject that represents the current state.
|
43
|
+
|
44
|
+
Parameters: `initialState`: The initial value of the state. This value can be of any type, determined by the generic
|
45
|
+
type `T`.
|
46
|
+
|
47
|
+
Returns a `StateObject` of type `T` representing the initialized state. This `StateObject` is an instance
|
48
|
+
of `ValidState`, which
|
49
|
+
encapsulates the state value and provides a `setValue` function to update it.
|
50
|
+
|
51
|
+
### `useEmptyState<T>`
|
52
|
+
|
53
|
+
This hook initializes a `StateObject` with an empty state. It is useful as a fallback when no valid state is available.
|
54
|
+
|
55
|
+
Parameters: None.
|
56
|
+
|
57
|
+
Returns a `StateObject` of type `T` representing an empty state. This `StateObject` is an instance of `EmptyState`,
|
58
|
+
which encapsulates the absence of a state value.
|
59
|
+
|
60
|
+
### `useElementState<T>`
|
61
|
+
|
62
|
+
This hook allows you to derive and update a specific element in an array within a `StateObject`. It is useful for
|
63
|
+
managing state at a granular level within an array.
|
64
|
+
|
65
|
+
Parameters:
|
66
|
+
|
67
|
+
- `state`: The `StateObject` containing an array.
|
68
|
+
- `index`: The index of the element to be derived.
|
69
|
+
|
70
|
+
Returns a `StateObject` of type `T` representing the element at the given index. If the index is out of bounds or the
|
71
|
+
state is empty, it returns an instance of `EmptyState`. Otherwise, it returns an instance of `ValidState`, which
|
72
|
+
encapsulates the element value and provides a `setValue` function to update it.
|
73
|
+
|
74
|
+
### `useFieldState<TOriginal, TField>`
|
75
|
+
|
76
|
+
This hook derives a field from the state object and creates a new `StateObject` for the field's value. It is useful for
|
77
|
+
managing state at a granular level within an object.
|
78
|
+
|
79
|
+
Parameters:
|
80
|
+
|
81
|
+
- `state`: The `StateObject` containing the original state.
|
82
|
+
- `field`: The field name to be derived from the state.
|
83
|
+
|
84
|
+
Returns a `StateObject` of type `TField` representing the derived field. This `StateObject` is an instance
|
85
|
+
of `ValidState`, which encapsulates the field value and provides a `setValue` function to update it.
|
86
|
+
|
87
|
+
For example:
|
88
|
+
|
89
|
+
```jsx
|
90
|
+
import React from 'react';
|
91
|
+
import {useStateObject, useFieldState} from 'react-state-monad';
|
92
|
+
|
93
|
+
const MyComponent = () => {
|
94
|
+
const userState = useStateObject({
|
95
|
+
name: 'John Doe',
|
96
|
+
age: 30,
|
97
|
+
});
|
98
|
+
|
99
|
+
const nameState = useFieldState(userState, 'name');
|
100
|
+
const ageState = useFieldState(userState, 'age');
|
101
|
+
|
102
|
+
return (
|
103
|
+
<div>
|
104
|
+
<input
|
105
|
+
type="text"
|
106
|
+
value={nameState.value}
|
107
|
+
onChange={(e) => nameState.value = e.target.value}
|
108
|
+
/>
|
109
|
+
<input
|
110
|
+
type="number"
|
111
|
+
value={ageState.value}
|
112
|
+
onChange={(e) => ageState.value = parseInt(e.target.value, 10)}
|
113
|
+
/>
|
114
|
+
</div>
|
115
|
+
);
|
116
|
+
};
|
117
|
+
|
118
|
+
export default MyComponent;
|
119
|
+
```
|
120
|
+
|
121
|
+
### `useRemapArray<T>`
|
122
|
+
|
123
|
+
This hook maps each element in an array within a `StateObject` to a new `StateObject`, allowing for independent updates
|
124
|
+
of each element while keeping the overall array state synchronized.
|
125
|
+
|
126
|
+
Parameters:
|
127
|
+
|
128
|
+
- `state`: The `StateObject` containing an array.
|
129
|
+
|
130
|
+
Returns an array of new `StateObject`s, each representing an element in the original array. This allows individual
|
131
|
+
updates while keeping the array state synchronized. If the state has no value, it returns an empty array.
|
132
|
+
|
133
|
+
### Complete Example
|
134
|
+
|
135
|
+
Here's a more complete example demonstrating the usage of `useStateObject`, `useFieldState`, `useElementState`,
|
136
|
+
and `useRemapArray` hooks in a React component:
|
137
|
+
|
138
|
+
```tsx
|
139
|
+
|
140
|
+
const AgeField = (props: { ageState: StateObject<number> }) => <div>
|
141
|
+
<label>Age:</label>
|
142
|
+
<input
|
143
|
+
type="number"
|
144
|
+
value={props.ageState.value}
|
145
|
+
onChange={x => props.ageState.value = parseInt(x.target.value, 10)}
|
146
|
+
/>
|
147
|
+
</div>;
|
148
|
+
|
149
|
+
|
150
|
+
const NameField = (props: { nameState: StateObject<string> }) => {
|
151
|
+
return <div>
|
152
|
+
<label>Name:</label>
|
153
|
+
<input
|
154
|
+
type="text"
|
155
|
+
value={props.nameState.value}
|
156
|
+
onChange={x => props.nameState.value = x.target.value}
|
157
|
+
/>
|
158
|
+
</div>;
|
159
|
+
}
|
160
|
+
|
161
|
+
const HobbyField = (props: { hobbyState: StateObject<string> }) => {
|
162
|
+
return <div>
|
163
|
+
<input
|
164
|
+
type="text"
|
165
|
+
value={props.hobbyState.value}
|
166
|
+
onChange={x => props.hobbyState.value = x.target.value}
|
167
|
+
/>
|
168
|
+
</div>;
|
169
|
+
}
|
170
|
+
|
171
|
+
const HobbiesField = (props: { hobbiesState: StateObject<string[]> }) => {
|
172
|
+
|
173
|
+
const hobbyStates: StateObject<string>[] = useRemapArray(props.hobbiesState);
|
174
|
+
|
175
|
+
const addHobby = () => {
|
176
|
+
// Always use the setter to update arrays, do not modify them directly to ensure React state consistency.
|
177
|
+
// Immutability is key 💗
|
178
|
+
props.hobbiesState.value = [...props.hobbiesState.value, ''];
|
179
|
+
}
|
180
|
+
|
181
|
+
return <div>
|
182
|
+
<label>Hobbies:</label>
|
183
|
+
{
|
184
|
+
hobbyStates.map((hobbyState, index) => <HobbyField key={index} hobbyState={hobbyState}/>)
|
185
|
+
}
|
186
|
+
<button onClick={addHobby}>Add Hobby</button>
|
187
|
+
</div>;
|
188
|
+
|
189
|
+
|
190
|
+
};
|
191
|
+
|
192
|
+
export const UserProfile = () => {
|
193
|
+
|
194
|
+
type DudeData = {
|
195
|
+
name: string;
|
196
|
+
age: number;
|
197
|
+
hobbies: string[];
|
198
|
+
}
|
199
|
+
// Initialize state with an object containing user details and an array of hobbies
|
200
|
+
const userState: StateObject<DudeData> = useStateObject({
|
201
|
+
name: 'John Doe',
|
202
|
+
age: 30,
|
203
|
+
hobbies: ['Reading', 'Traveling', 'Cooking'],
|
204
|
+
});
|
205
|
+
|
206
|
+
// Derive state for individual fields
|
207
|
+
const nameState: StateObject<string> = useFieldState(userState, 'name');
|
208
|
+
const ageState: StateObject<number> = useFieldState(userState, 'age');
|
209
|
+
|
210
|
+
// Derive state for hobbies array
|
211
|
+
const hobbiesState: StateObject<string[]> = useFieldState(userState, 'hobbies');
|
212
|
+
|
213
|
+
|
214
|
+
return (
|
215
|
+
<div>
|
216
|
+
<h1>User Profile</h1>
|
217
|
+
<NameField nameState={nameState}/>
|
218
|
+
<AgeField ageState={ageState}/>
|
219
|
+
<HobbiesField hobbiesState={hobbiesState}/>
|
220
|
+
</div>
|
221
|
+
);
|
222
|
+
};
|
223
|
+
```
|
224
|
+
|
225
|
+
## Contributing
|
226
|
+
|
227
|
+
Contributions are welcome! If you'd like to contribute to this library, please fork the repository and submit a pull
|
228
|
+
request.
|
229
|
+
|
230
|
+
How to Contribute
|
231
|
+
Fork the repository.
|
232
|
+
|
233
|
+
* Create a new branch for your feature `git checkout -b feature-name`
|
234
|
+
* Commit your changes `git commit -am 'Add new feature'`
|
235
|
+
* Push to the branch `git push origin feature-name`
|
236
|
+
* Open a pull request. I'll be happy to review it!
|
237
|
+
|
238
|
+
## License
|
239
|
+
|
240
|
+
This project is licensed under the GPL-3.0 License.
|
241
|
+
|
242
|
+
## Author
|
243
|
+
|
244
|
+
`Marcos Alvarez`
|
245
|
+
|
246
|
+
[<img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" width="38" height="38">](https://github.com/alvmivan)
|
247
|
+
[<img src="https://www.linkedin.com/favicon.ico" width="40" height="40">](https://www.linkedin.com/in/marcos-alvarez-40651b150/)
|
248
|
+
[<img src="https://ssl.gstatic.com/ui/v1/icons/mail/rfr/gmail.ico" width="40" height="40">](mailto:alvmivan@gmail.com)
|
package/dist/index.cjs
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __create = Object.create;
|
3
|
+
var __defProp = Object.defineProperty;
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
10
|
+
for (let key of __getOwnPropNames(from))
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
13
|
+
}
|
14
|
+
return to;
|
15
|
+
};
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
22
|
+
mod
|
23
|
+
));
|
24
|
+
|
25
|
+
// src/index.tsx
|
26
|
+
var import_react2 = __toESM(require("react"), 1);
|
27
|
+
|
28
|
+
// src/hooks/useStateObject.ts
|
29
|
+
var import_react = require("react");
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "react-state-monad",
|
3
|
+
"type": "module",
|
4
|
+
"version": "0.0.1",
|
5
|
+
"description": "A set of hooks to manage/transform/filter states with monads in React",
|
6
|
+
"keywords": [
|
7
|
+
"maybe",
|
8
|
+
"option",
|
9
|
+
"monad",
|
10
|
+
"state",
|
11
|
+
"monads",
|
12
|
+
"react",
|
13
|
+
"functional"
|
14
|
+
],
|
15
|
+
"scripts": {
|
16
|
+
"validateTypes": "tsc --noEmit",
|
17
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
18
|
+
"cleanBuild" : "rm -rf dist"
|
19
|
+
},
|
20
|
+
"dependencies": {
|
21
|
+
"react": "^19.0.0"
|
22
|
+
},
|
23
|
+
"devDependencies": {
|
24
|
+
"@types/react": "^19.0.7",
|
25
|
+
"tsup": "^8.3.5",
|
26
|
+
"typescript": "^5.7.3"
|
27
|
+
},
|
28
|
+
"author": {
|
29
|
+
"name": "Marcos Alvarez",
|
30
|
+
"email": "alvmivan@gmail.com",
|
31
|
+
"url": "https://github.com/alvmivan"
|
32
|
+
},
|
33
|
+
"license": "GPL-3.0",
|
34
|
+
"main": "dist/index.js",
|
35
|
+
"module": "dist/index.mjs",
|
36
|
+
"types": "dist/types/index.d.ts"
|
37
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* Helper type that ensures a field is a valid key of an object and that the field's type matches the expected type.
|
3
|
+
*
|
4
|
+
* @template TObject - The object type.
|
5
|
+
* @template TField - The expected type of the field.
|
6
|
+
*/
|
7
|
+
export type ValidFieldFrom<TObject, TField> = {
|
8
|
+
[Key in keyof TObject]: TObject[Key] extends TField ? Key : never;
|
9
|
+
}[keyof TObject];
|
10
|
+
|
11
|
+
|
12
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import {StateObject} from "../stateObject";
|
2
|
+
import {EmptyState} from "../implementations/emptyState";
|
3
|
+
import {ValidState} from "../implementations/validState";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Hook that allows you to derive and update a specific element in an array within a StateObject.
|
7
|
+
*
|
8
|
+
* @template T - The type of the array elements.
|
9
|
+
* @param state - The StateObject containing an array.
|
10
|
+
* @param index - The index of the element to be derived.
|
11
|
+
* @returns A new StateObject representing the element at the given index.
|
12
|
+
*/
|
13
|
+
export function useElementState<T>(state: StateObject<T[]>, index: number): StateObject<T> {
|
14
|
+
if (!state.hasValue || index < 0 || index >= state.value.length) {
|
15
|
+
return new EmptyState<T>(); // Returns an empty state if the index is out of bounds or state is empty.
|
16
|
+
}
|
17
|
+
|
18
|
+
return new ValidState<T>(
|
19
|
+
state.value[index],
|
20
|
+
(newElement) => {
|
21
|
+
const arrayCopy = [...state.value];
|
22
|
+
arrayCopy[index] = newElement;
|
23
|
+
state.value = arrayCopy;
|
24
|
+
}
|
25
|
+
);
|
26
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import {StateObject} from "../stateObject";
|
2
|
+
import {EmptyState} from "../implementations/emptyState";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Hook that initializes a StateObject with an empty state.
|
6
|
+
* This is useful as a fallback when no valid state is available.
|
7
|
+
*
|
8
|
+
* @template T - The type of the value that could be held by the state.
|
9
|
+
* @returns A StateObject representing an empty state.
|
10
|
+
*/
|
11
|
+
export function useEmptyState<T>(): StateObject<T> {
|
12
|
+
return new EmptyState<T>(); // Returns a new EmptyState object.
|
13
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import {StateObject} from "../stateObject";
|
2
|
+
import {ValidFieldFrom} from "./types";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Hook that derives a field from the state object and creates a new StateObject for the field's value.
|
6
|
+
*
|
7
|
+
* @template TOriginal - The type of the original state object.
|
8
|
+
* @template TField - The type of the field value to be derived.
|
9
|
+
* @param state - The StateObject containing the original state.
|
10
|
+
* @param field - The field name to be derived from the state.
|
11
|
+
* @returns A new StateObject for the derived field.
|
12
|
+
*/
|
13
|
+
export function useFieldState<TOriginal, TField>(
|
14
|
+
state: StateObject<TOriginal>,
|
15
|
+
field: ValidFieldFrom<TOriginal, TField>
|
16
|
+
): StateObject<TField> {
|
17
|
+
return state.map(
|
18
|
+
(original) => original[field] as TField, // Extracts the field value.
|
19
|
+
(newField, original) => ({...original, [field]: newField} as TOriginal) // Updates the field with the new value.
|
20
|
+
);
|
21
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import {StateObject} from "../stateObject";
|
2
|
+
import {ValidState} from "../implementations/validState";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Hook that maps each element in an array within a StateObject to a new StateObject,
|
6
|
+
* allowing for independent updates of each element while keeping the overall array state synchronized.
|
7
|
+
*
|
8
|
+
* @template T - The type of the array elements.
|
9
|
+
* @param state - The StateObject containing an array.
|
10
|
+
* @returns An array of new StateObjects, each representing an element in the original array,
|
11
|
+
* allowing individual updates while keeping the array state synchronized.
|
12
|
+
*/
|
13
|
+
export function useRemapArray<T>(state: StateObject<T[]>): StateObject<T>[] {
|
14
|
+
if (!state.hasValue) return [] // Returns an empty array if the state has no value.
|
15
|
+
|
16
|
+
const count = state.value.length
|
17
|
+
const result: StateObject<T>[] = []
|
18
|
+
|
19
|
+
for (let i = 0; i < count; i++) {
|
20
|
+
result.push(
|
21
|
+
new ValidState<T>(
|
22
|
+
state.value[i], // The current value of the element at index i.
|
23
|
+
(newElement) => { // Setter to update the element at index i in the array.
|
24
|
+
const arrayCopy = [...state.value]; // Create a copy of the original array.
|
25
|
+
arrayCopy[i] = newElement; // Replace the element at index i with the new element.
|
26
|
+
state.value = arrayCopy; // Update the state with the new array, triggering a re-render.
|
27
|
+
}
|
28
|
+
)
|
29
|
+
)
|
30
|
+
}
|
31
|
+
|
32
|
+
return result // Return the array of StateObjects representing each element.
|
33
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import {StateObject} from "../stateObject";
|
2
|
+
import {useState} from "react";
|
3
|
+
import {ValidState} from "../implementations/validState";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Hook that initializes a StateObject with the given initial value.
|
7
|
+
*
|
8
|
+
* @template T - The type of the value to be stored in the state.
|
9
|
+
* @param initialState - The initial value of the state.
|
10
|
+
* @returns A StateObject representing the initialized state.
|
11
|
+
*/
|
12
|
+
export function useStateObject<T>(initialState: T): StateObject<T> {
|
13
|
+
const [state, setState] = useState<T>(initialState);
|
14
|
+
return new ValidState<T>(state, setState); // Returns a new ValidState object with the initial state value.
|
15
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import {StateObject} from "../stateObject";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Represents a state that holds no value and is considered "empty".
|
5
|
+
* This is used as a fallback or default when there is no valid state.
|
6
|
+
* you should NEVER use this class directly, use the `StateObject` interface instead.
|
7
|
+
* and create instances by using the hooks
|
8
|
+
* @template T - The type of the value that could be held by the state.
|
9
|
+
*/
|
10
|
+
export class EmptyState<T> implements StateObject<T> {
|
11
|
+
// No value stored, returns an error when accessed.
|
12
|
+
get value(): T {
|
13
|
+
throw new Error("Not implemented");
|
14
|
+
}
|
15
|
+
|
16
|
+
get hasValue(): boolean {
|
17
|
+
return false;
|
18
|
+
}
|
19
|
+
|
20
|
+
orElse(orElse: T): T {
|
21
|
+
return orElse; // Returns the fallback value when the state is empty.
|
22
|
+
}
|
23
|
+
|
24
|
+
do() {
|
25
|
+
// No operation for empty state.
|
26
|
+
}
|
27
|
+
|
28
|
+
filter(): StateObject<T> {
|
29
|
+
return this; // The empty state remains unchanged when filtered.
|
30
|
+
}
|
31
|
+
|
32
|
+
set value(_: T) {
|
33
|
+
// No operation for setting a value in the empty state.
|
34
|
+
}
|
35
|
+
|
36
|
+
flatMap<U>(): StateObject<U> {
|
37
|
+
return new EmptyState<U>(); // Returns an empty state when flatMapped.
|
38
|
+
}
|
39
|
+
|
40
|
+
map<U>(): StateObject<U> {
|
41
|
+
return new EmptyState<U>(); // Returns an empty state when mapped.
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import {EmptyState} from "./emptyState";
|
2
|
+
import {StateObject} from "../stateObject";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Represents a state that holds a valid value of type T.
|
6
|
+
* you should NEVER use this class directly, use the `StateObject` interface instead.
|
7
|
+
* and create instances by using the hooks
|
8
|
+
* @template T - The type of the value stored in the state.
|
9
|
+
*/
|
10
|
+
export class ValidState<T> implements StateObject<T> {
|
11
|
+
private readonly state: T;
|
12
|
+
private readonly setter: (state: T) => void;
|
13
|
+
|
14
|
+
constructor(state: T, setter: (state: T) => void) {
|
15
|
+
this.state = state;
|
16
|
+
this.setter = setter;
|
17
|
+
}
|
18
|
+
|
19
|
+
get value(): T {
|
20
|
+
return this.state;
|
21
|
+
}
|
22
|
+
|
23
|
+
do(action: (t: T) => void) {
|
24
|
+
action(this.state); // Performs the given action on the state value.
|
25
|
+
}
|
26
|
+
|
27
|
+
orElse() {
|
28
|
+
return this.state; // Returns the state value as it is valid.
|
29
|
+
}
|
30
|
+
|
31
|
+
set value(newState: T) {
|
32
|
+
this.setter(newState); // Sets a new value for the state.
|
33
|
+
}
|
34
|
+
|
35
|
+
map<U>(
|
36
|
+
mappingFunction: (t: T) => U,
|
37
|
+
inverseMappingFunction: (u: U, t: T) => T
|
38
|
+
): StateObject<U> {
|
39
|
+
const derivedState = mappingFunction(this.state);
|
40
|
+
const derivedSetter = (newState: U) => {
|
41
|
+
this.setter(inverseMappingFunction(newState, this.state)); // Updates the state with the inverse mapping.
|
42
|
+
};
|
43
|
+
|
44
|
+
return new ValidState<U>(derivedState, derivedSetter); // Returns a new state object with the transformed value.
|
45
|
+
}
|
46
|
+
|
47
|
+
flatMap<U>(
|
48
|
+
mappingFunction: (t: T) => StateObject<U>
|
49
|
+
): StateObject<U> {
|
50
|
+
return mappingFunction(this.state); // Applies the mapping function and returns the result.
|
51
|
+
}
|
52
|
+
|
53
|
+
get hasValue(): boolean {
|
54
|
+
return true;
|
55
|
+
}
|
56
|
+
|
57
|
+
filter(predicate: (t: T) => boolean): StateObject<T> {
|
58
|
+
return predicate(this.state) ? (this as StateObject<T>) : new EmptyState<T>(); // Filters the state based on the predicate.
|
59
|
+
}
|
60
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
/**
|
2
|
+
* Represents a state object that holds a value of type T, allowing various state operations.
|
3
|
+
* This is the main interface for managing state, with operations like `map`, `filter`, and `flatMap`.
|
4
|
+
* initialize with useStateObject<T>(initialState: T) hook
|
5
|
+
* @template T - The type of the value stored in the state object.
|
6
|
+
*/
|
7
|
+
export type StateObject<T> = {
|
8
|
+
/**
|
9
|
+
* The current value of the state.
|
10
|
+
*/
|
11
|
+
get value(): T;
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Returns true if the state has a valid value, false otherwise.
|
15
|
+
*/
|
16
|
+
get hasValue(): boolean;
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Performs an action on the current state value.
|
20
|
+
*
|
21
|
+
* @param action - A function that accepts the current state value and performs some operation.
|
22
|
+
*/
|
23
|
+
do(action: (t: T) => void): void;
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Sets a new value for the state.
|
27
|
+
*
|
28
|
+
* @param newState - The new state value to set.
|
29
|
+
*/
|
30
|
+
set value(newState: T);
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Transforms the current state into another state object by applying a mapping function.
|
34
|
+
*
|
35
|
+
* @template U - The type of the new state value.
|
36
|
+
* @param mappingFunction - A function that transforms the current state value into a new value.
|
37
|
+
* @param inverseMappingFunction - A function that transforms a new value back to the original state type.
|
38
|
+
* @returns A new StateObject with the transformed value.
|
39
|
+
*/
|
40
|
+
map<U>(
|
41
|
+
mappingFunction: (t: T) => U,
|
42
|
+
inverseMappingFunction: (u: U, t: T) => T
|
43
|
+
): StateObject<U>;
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Filters the state based on a predicate, returning an empty state if the predicate is not satisfied.
|
47
|
+
*
|
48
|
+
* @param predicate - A function that tests the current state value.
|
49
|
+
* @returns A new StateObject with the original value or an empty state.
|
50
|
+
*/
|
51
|
+
filter(predicate: (t: T) => boolean): StateObject<T>;
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Returns the current state value if it exists; otherwise, returns the provided alternative value.
|
55
|
+
*
|
56
|
+
* @param orElse - The value to return if the state does not have a valid value.
|
57
|
+
* @returns The current state value or the provided fallback value.
|
58
|
+
*/
|
59
|
+
orElse(orElse: T): T;
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Transforms the current state into another state object by applying a mapping function that returns a new state.
|
63
|
+
*
|
64
|
+
* @template U - The type of the new state value.
|
65
|
+
* @param mappingFunction - A function that transforms the current state value into another state object.
|
66
|
+
* @returns A new StateObject based on the result of the mapping function.
|
67
|
+
*/
|
68
|
+
flatMap<U>(
|
69
|
+
mappingFunction: (t: T) => StateObject<U>
|
70
|
+
): StateObject<U>;
|
71
|
+
}
|
package/tsconfig.json
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"target": "esnext", // Usar un target moderno compatible
|
4
|
+
"module": "esnext", // Usar módulos ES
|
5
|
+
"moduleResolution": "node", // Resolución de módulos tipo Node
|
6
|
+
"declaration": true, // Generar los archivos .d.ts
|
7
|
+
"declarationDir": "./dist/types", // El directorio de las declaraciones de tipo
|
8
|
+
"outDir": "./dist", // El directorio de salida para el bundle
|
9
|
+
"strict": true, // Habilitar el modo estricto
|
10
|
+
"esModuleInterop": true, // Interoperabilidad con módulos ES
|
11
|
+
"skipLibCheck": true, // Omitir la comprobación de bibliotecas
|
12
|
+
"forceConsistentCasingInFileNames": true // Forzar la consistencia de las mayúsculas/minúsculas en los nombres de archivo
|
13
|
+
},
|
14
|
+
"include": [
|
15
|
+
"src/**/*" // Incluir los archivos fuente de la carpeta src
|
16
|
+
],
|
17
|
+
"exclude": [
|
18
|
+
"node_modules" // Excluir node_modules
|
19
|
+
]
|
20
|
+
}
|