fivem-nui-react 1.1.0 → 1.2.0
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/LICENSE +21 -0
- package/README.md +114 -146
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +71 -11
- package/dist/index.mjs +71 -11
- package/package.json +10 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mustafa Burak Güneş
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -10,28 +10,46 @@ npm install fivem-nui-react
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **useSendNui** - Hook for sending data without expecting a response
|
|
17
|
-
- **isEnvBrowser** - Check if running in browser (debug mode)
|
|
18
|
-
- **Mock data support** - Test your UI in browser with simulated responses
|
|
13
|
+
```tsx
|
|
14
|
+
// Listen for NUI messages from FiveM client
|
|
15
|
+
useNuiEvent("eventName", (data) => {});
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
// Send request and get response
|
|
18
|
+
const data = await fetchNui("eventName", { payload });
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
// Hook with loading/error states and callback
|
|
21
|
+
const [fetch, { loading, error }] = useNuiCallback("eventName", (data) => {});
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
// Send data without expecting response
|
|
24
|
+
const [send, { loading, error }] = useSendNui("eventName");
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Check if running in browser (debug mode)
|
|
27
|
+
if (isEnvBrowser())
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
```lua
|
|
31
|
+
-- Send message from FiveM client to UI
|
|
32
|
+
SendNUIMessage({ action = "eventName", data = { key = "value" } })
|
|
33
|
+
|
|
34
|
+
-- Register callback for UI requests
|
|
35
|
+
RegisterNUICallback("eventName", function(data, cb)
|
|
36
|
+
cb({ response = "data" })
|
|
37
|
+
end)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### useNuiEvent
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
Listen for NUI messages sent from FiveM client.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
useNuiEvent<T>(
|
|
48
|
+
action: string,
|
|
49
|
+
handler: (data: T) => void,
|
|
50
|
+
options?: { mockData?: T; mockDelay?: number }
|
|
51
|
+
): void
|
|
52
|
+
```
|
|
35
53
|
|
|
36
54
|
**Example:**
|
|
37
55
|
|
|
@@ -41,11 +59,6 @@ import { useNuiEvent } from "fivem-nui-react";
|
|
|
41
59
|
function App() {
|
|
42
60
|
const [visible, setVisible] = useState(false);
|
|
43
61
|
|
|
44
|
-
useNuiEvent<{ show: boolean }>("toggleUI", (data) => {
|
|
45
|
-
setVisible(data.show);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// With mock data for browser testing
|
|
49
62
|
useNuiEvent<{ show: boolean }>("toggleUI", (data) => setVisible(data.show), {
|
|
50
63
|
mockData: { show: true },
|
|
51
64
|
mockDelay: 1000,
|
|
@@ -55,7 +68,7 @@ function App() {
|
|
|
55
68
|
}
|
|
56
69
|
```
|
|
57
70
|
|
|
58
|
-
**
|
|
71
|
+
**FiveM Client (Lua):**
|
|
59
72
|
|
|
60
73
|
```lua
|
|
61
74
|
SendNUIMessage({
|
|
@@ -68,53 +81,53 @@ SendNUIMessage({
|
|
|
68
81
|
|
|
69
82
|
### fetchNui
|
|
70
83
|
|
|
71
|
-
Send a request to the
|
|
84
|
+
Send a request to the FiveM client and receive a response.
|
|
72
85
|
|
|
73
86
|
```tsx
|
|
74
|
-
fetchNui<T, D>(
|
|
87
|
+
fetchNui<T, D>(
|
|
88
|
+
eventName: string,
|
|
89
|
+
data?: D,
|
|
90
|
+
options?: { mockData?: T; mockDelay?: number; signal?: AbortSignal }
|
|
91
|
+
): Promise<T>
|
|
75
92
|
```
|
|
76
93
|
|
|
77
|
-
**Parameters:**
|
|
78
|
-
|
|
79
|
-
- `eventName` - The NUI callback event name
|
|
80
|
-
- `data` - Data to send to Lua
|
|
81
|
-
- `options` - Optional mock data configuration for browser testing
|
|
82
|
-
|
|
83
94
|
**Example:**
|
|
84
95
|
|
|
85
96
|
```tsx
|
|
86
97
|
import { fetchNui } from "fivem-nui-react";
|
|
87
98
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
const player = await fetchNui<PlayerData>(
|
|
100
|
+
"getPlayerData",
|
|
101
|
+
{ id: 1 },
|
|
102
|
+
{
|
|
103
|
+
mockData: { name: "John", level: 10 },
|
|
104
|
+
mockDelay: 500,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
```
|
|
92
108
|
|
|
93
|
-
|
|
94
|
-
const player = await fetchNui<PlayerData>("getPlayerData", { id: 1 });
|
|
95
|
-
console.log(player.name, player.level);
|
|
96
|
-
}
|
|
109
|
+
**With AbortController:**
|
|
97
110
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
```tsx
|
|
112
|
+
const controller = new AbortController();
|
|
113
|
+
|
|
114
|
+
const player = await fetchNui<PlayerData>(
|
|
115
|
+
"getPlayerData",
|
|
116
|
+
{ id: 1 },
|
|
117
|
+
{
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Cancel the request
|
|
123
|
+
controller.abort();
|
|
110
124
|
```
|
|
111
125
|
|
|
112
|
-
**Lua
|
|
126
|
+
**FiveM Client (Lua):**
|
|
113
127
|
|
|
114
128
|
```lua
|
|
115
129
|
RegisterNUICallback("getPlayerData", function(data, cb)
|
|
116
130
|
local playerId = data.id
|
|
117
|
-
-- Fetch player data...
|
|
118
131
|
cb({ name = "John", level = 10 })
|
|
119
132
|
end)
|
|
120
133
|
```
|
|
@@ -123,48 +136,28 @@ end)
|
|
|
123
136
|
|
|
124
137
|
### useNuiCallback
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
Hook for NUI requests with loading/error states. Calls the callback with response data.
|
|
127
140
|
|
|
128
141
|
```tsx
|
|
129
142
|
useNuiCallback<T, D>(
|
|
130
143
|
eventName: string,
|
|
131
144
|
callback: (data: T) => void,
|
|
132
|
-
options?:
|
|
133
|
-
): [
|
|
145
|
+
options?: { mockData?: T; mockDelay?: number }
|
|
146
|
+
): [(data?: D) => Promise<T>, { loading: boolean; error: Error | null }]
|
|
134
147
|
```
|
|
135
148
|
|
|
136
|
-
**Parameters:**
|
|
137
|
-
|
|
138
|
-
- `eventName` - The NUI callback event name
|
|
139
|
-
- `callback` - Callback function executed when response is received
|
|
140
|
-
- `options` - Optional mock data configuration for browser testing
|
|
141
|
-
|
|
142
|
-
**Returns:**
|
|
143
|
-
|
|
144
|
-
- `fetchFn` - Function to trigger the request
|
|
145
|
-
- `state.loading` - Boolean indicating if request is in progress
|
|
146
|
-
- `state.error` - Error object if request failed
|
|
147
|
-
|
|
148
149
|
**Example:**
|
|
149
150
|
|
|
150
151
|
```tsx
|
|
151
152
|
import { useNuiCallback } from "fivem-nui-react";
|
|
152
153
|
|
|
153
|
-
interface PlayerData {
|
|
154
|
-
name: string;
|
|
155
|
-
level: number;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
154
|
function PlayerInfo() {
|
|
159
155
|
const [player, setPlayer] = useState<PlayerData | null>(null);
|
|
160
156
|
|
|
161
157
|
const [fetchPlayer, { loading, error }] = useNuiCallback<PlayerData>(
|
|
162
158
|
"getPlayerData",
|
|
163
159
|
(data) => setPlayer(data),
|
|
164
|
-
{
|
|
165
|
-
mockData: { name: "John", level: 10 },
|
|
166
|
-
mockDelay: 500,
|
|
167
|
-
},
|
|
160
|
+
{ mockData: { name: "John", level: 10 }, mockDelay: 500 },
|
|
168
161
|
);
|
|
169
162
|
|
|
170
163
|
useEffect(() => {
|
|
@@ -173,41 +166,33 @@ function PlayerInfo() {
|
|
|
173
166
|
|
|
174
167
|
if (loading) return <div>Loading...</div>;
|
|
175
168
|
if (error) return <div>Error: {error.message}</div>;
|
|
176
|
-
if (!player) return null;
|
|
177
169
|
|
|
178
|
-
return
|
|
179
|
-
<div>
|
|
180
|
-
<p>Name: {player.name}</p>
|
|
181
|
-
<p>Level: {player.level}</p>
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
170
|
+
return <div>{player?.name}</div>;
|
|
184
171
|
}
|
|
185
172
|
```
|
|
186
173
|
|
|
174
|
+
**FiveM Client (Lua):**
|
|
175
|
+
|
|
176
|
+
```lua
|
|
177
|
+
RegisterNUICallback("getPlayerData", function(data, cb)
|
|
178
|
+
local playerId = data.id
|
|
179
|
+
cb({ name = "John", level = 10 })
|
|
180
|
+
end)
|
|
181
|
+
```
|
|
182
|
+
|
|
187
183
|
---
|
|
188
184
|
|
|
189
185
|
### useSendNui
|
|
190
186
|
|
|
191
|
-
|
|
187
|
+
Hook for sending data to FiveM client without expecting a response.
|
|
192
188
|
|
|
193
189
|
```tsx
|
|
194
190
|
useSendNui<D>(
|
|
195
191
|
eventName: string,
|
|
196
|
-
options?:
|
|
197
|
-
): [
|
|
192
|
+
options?: { mockDelay?: number }
|
|
193
|
+
): [(data?: D) => Promise<void>, { loading: boolean; error: Error | null }]
|
|
198
194
|
```
|
|
199
195
|
|
|
200
|
-
**Parameters:**
|
|
201
|
-
|
|
202
|
-
- `eventName` - The NUI callback event name
|
|
203
|
-
- `options` - Optional mock delay configuration for browser testing
|
|
204
|
-
|
|
205
|
-
**Returns:**
|
|
206
|
-
|
|
207
|
-
- `sendFn` - Function to send data
|
|
208
|
-
- `state.loading` - Boolean indicating if request is in progress
|
|
209
|
-
- `state.error` - Error object if request failed
|
|
210
|
-
|
|
211
196
|
**Example:**
|
|
212
197
|
|
|
213
198
|
```tsx
|
|
@@ -216,19 +201,18 @@ import { useSendNui } from "fivem-nui-react";
|
|
|
216
201
|
function CloseButton() {
|
|
217
202
|
const [closeUI, { loading }] = useSendNui<{ reason: string }>("closeUI");
|
|
218
203
|
|
|
219
|
-
const handleClose = () => {
|
|
220
|
-
closeUI({ reason: "user_clicked" });
|
|
221
|
-
};
|
|
222
|
-
|
|
223
204
|
return (
|
|
224
|
-
<button
|
|
225
|
-
{
|
|
205
|
+
<button
|
|
206
|
+
onClick={() => closeUI({ reason: "user_clicked" })}
|
|
207
|
+
disabled={loading}
|
|
208
|
+
>
|
|
209
|
+
Close
|
|
226
210
|
</button>
|
|
227
211
|
);
|
|
228
212
|
}
|
|
229
213
|
```
|
|
230
214
|
|
|
231
|
-
**Lua
|
|
215
|
+
**FiveM Client (Lua):**
|
|
232
216
|
|
|
233
217
|
```lua
|
|
234
218
|
RegisterNUICallback("closeUI", function(data, cb)
|
|
@@ -243,80 +227,64 @@ end)
|
|
|
243
227
|
|
|
244
228
|
Check if running in browser (outside FiveM).
|
|
245
229
|
|
|
246
|
-
```tsx
|
|
247
|
-
isEnvBrowser(): boolean
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**Example:**
|
|
251
|
-
|
|
252
230
|
```tsx
|
|
253
231
|
import { isEnvBrowser } from "fivem-nui-react";
|
|
254
232
|
|
|
255
233
|
if (isEnvBrowser()) {
|
|
256
234
|
console.log("Running in browser - debug mode");
|
|
257
|
-
} else {
|
|
258
|
-
console.log("Running in FiveM");
|
|
259
235
|
}
|
|
260
236
|
```
|
|
261
237
|
|
|
262
238
|
---
|
|
263
239
|
|
|
264
|
-
## Browser Testing
|
|
240
|
+
## Browser Testing
|
|
265
241
|
|
|
266
|
-
All hooks
|
|
242
|
+
All hooks support mock data for testing in browser without FiveM:
|
|
267
243
|
|
|
268
|
-
|
|
244
|
+
| Option | Description | Default |
|
|
245
|
+
| ----------- | ------------------------------------------------------ | ------- |
|
|
246
|
+
| `mockData` | Data to return in browser mode | - |
|
|
247
|
+
| `mockDelay` | Delay in ms before returning | 500 |
|
|
248
|
+
| `signal` | AbortSignal for request cancellation (`fetchNui` only) | - |
|
|
269
249
|
|
|
270
|
-
|
|
271
|
-
- `fetchNui` will return `mockData` after `mockDelay` milliseconds
|
|
272
|
-
- `useNuiCallback` will call the callback with `mockData` after `mockDelay` milliseconds
|
|
273
|
-
- `useSendNui` will simulate a delay with `mockDelay` milliseconds
|
|
250
|
+
When `isEnvBrowser()` is `true`:
|
|
274
251
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
- `
|
|
278
|
-
- `
|
|
252
|
+
- `useNuiEvent` triggers handler with `mockData` after delay
|
|
253
|
+
- `fetchNui` returns `mockData` after delay
|
|
254
|
+
- `useNuiCallback` calls callback with `mockData` after delay
|
|
255
|
+
- `useSendNui` simulates delay only
|
|
279
256
|
|
|
280
257
|
---
|
|
281
258
|
|
|
282
|
-
## TypeScript
|
|
259
|
+
## TypeScript
|
|
283
260
|
|
|
284
|
-
All functions are fully typed
|
|
261
|
+
All functions are fully typed:
|
|
285
262
|
|
|
286
263
|
```tsx
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
title: string;
|
|
264
|
+
interface PlayerData {
|
|
265
|
+
name: string;
|
|
266
|
+
level: number;
|
|
291
267
|
}
|
|
292
268
|
|
|
293
269
|
interface PlayerRequest {
|
|
294
270
|
id: number;
|
|
295
271
|
}
|
|
296
272
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
level: number;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Use with type parameters
|
|
303
|
-
useNuiEvent<ShowUIData>("showUI", (data) => {
|
|
304
|
-
// data is typed as ShowUIData
|
|
305
|
-
console.log(data.visible, data.title);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const player = await fetchNui<PlayerResponse, PlayerRequest>("getPlayer", {
|
|
273
|
+
// Typed response
|
|
274
|
+
const player = await fetchNui<PlayerData, PlayerRequest>("getPlayer", {
|
|
309
275
|
id: 1,
|
|
310
276
|
});
|
|
311
|
-
// player is typed as PlayerResponse
|
|
312
277
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
278
|
+
// Typed callback
|
|
279
|
+
const [fetchPlayer, { loading }] = useNuiCallback<PlayerData, PlayerRequest>(
|
|
280
|
+
"getPlayer",
|
|
281
|
+
(data) => console.log(data.name),
|
|
282
|
+
);
|
|
317
283
|
```
|
|
318
284
|
|
|
319
|
-
|
|
285
|
+
## Note
|
|
286
|
+
|
|
287
|
+
Most of the code in this project was written by AI and then manually reviewed and edited.
|
|
320
288
|
|
|
321
289
|
## License
|
|
322
290
|
|
package/dist/index.d.mts
CHANGED
|
@@ -10,6 +10,7 @@ interface UseNuiEventOptions<T = unknown> {
|
|
|
10
10
|
interface FetchNuiOptions<T = unknown> {
|
|
11
11
|
mockData?: T;
|
|
12
12
|
mockDelay?: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Checks if running in browser debug mode
|
|
@@ -30,7 +31,7 @@ declare function useNuiEvent<T = unknown>(action: string, handler: NuiEventHandl
|
|
|
30
31
|
* Sends a request to NUI callback (fivem client)
|
|
31
32
|
* @param eventName - The callback event name
|
|
32
33
|
* @param data - Data to send
|
|
33
|
-
* @param options - Options for mock data in browser mode
|
|
34
|
+
* @param options - Options for mock data and abort signal in browser mode
|
|
34
35
|
* @returns Promise<T> - Response from fivem client
|
|
35
36
|
* @example
|
|
36
37
|
* const result = await fetchNui("getPlayerData", { id: 1 }, {
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ interface UseNuiEventOptions<T = unknown> {
|
|
|
10
10
|
interface FetchNuiOptions<T = unknown> {
|
|
11
11
|
mockData?: T;
|
|
12
12
|
mockDelay?: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Checks if running in browser debug mode
|
|
@@ -30,7 +31,7 @@ declare function useNuiEvent<T = unknown>(action: string, handler: NuiEventHandl
|
|
|
30
31
|
* Sends a request to NUI callback (fivem client)
|
|
31
32
|
* @param eventName - The callback event name
|
|
32
33
|
* @param data - Data to send
|
|
33
|
-
* @param options - Options for mock data in browser mode
|
|
34
|
+
* @param options - Options for mock data and abort signal in browser mode
|
|
34
35
|
* @returns Promise<T> - Response from fivem client
|
|
35
36
|
* @example
|
|
36
37
|
* const result = await fetchNui("getPlayerData", { id: 1 }, {
|
package/dist/index.js
CHANGED
|
@@ -66,9 +66,21 @@ async function fetchNui(eventName, data, options) {
|
|
|
66
66
|
headers: {
|
|
67
67
|
"Content-Type": "application/json; charset=UTF-8"
|
|
68
68
|
},
|
|
69
|
-
body: JSON.stringify(data ?? {})
|
|
69
|
+
body: JSON.stringify(data ?? {}),
|
|
70
|
+
signal: options == null ? void 0 : options.signal
|
|
70
71
|
});
|
|
71
|
-
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`NUI request failed: ${eventName} (${response.status})`);
|
|
74
|
+
}
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
if (!text || text.length === 0) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(text);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
72
84
|
}
|
|
73
85
|
function useNuiCallback(eventName, callback, options) {
|
|
74
86
|
const [state, setState] = (0, import_react.useState)({
|
|
@@ -76,20 +88,46 @@ function useNuiCallback(eventName, callback, options) {
|
|
|
76
88
|
error: null
|
|
77
89
|
});
|
|
78
90
|
const callbackRef = (0, import_react.useRef)(callback);
|
|
91
|
+
const mountedRef = (0, import_react.useRef)(true);
|
|
92
|
+
const abortControllerRef = (0, import_react.useRef)(null);
|
|
79
93
|
(0, import_react.useEffect)(() => {
|
|
80
94
|
callbackRef.current = callback;
|
|
81
95
|
}, [callback]);
|
|
96
|
+
(0, import_react.useEffect)(() => {
|
|
97
|
+
mountedRef.current = true;
|
|
98
|
+
return () => {
|
|
99
|
+
var _a;
|
|
100
|
+
mountedRef.current = false;
|
|
101
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
82
104
|
const fetch2 = (0, import_react.useCallback)(
|
|
83
105
|
async (data) => {
|
|
84
|
-
|
|
106
|
+
var _a;
|
|
107
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
abortControllerRef.current = controller;
|
|
110
|
+
if (mountedRef.current) {
|
|
111
|
+
setState({ loading: true, error: null });
|
|
112
|
+
}
|
|
85
113
|
try {
|
|
86
|
-
const result = await fetchNui(eventName, data,
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
const result = await fetchNui(eventName, data, {
|
|
115
|
+
...options,
|
|
116
|
+
signal: controller.signal
|
|
117
|
+
});
|
|
118
|
+
if (!controller.signal.aborted && mountedRef.current) {
|
|
119
|
+
callbackRef.current(result);
|
|
120
|
+
setState({ loading: false, error: null });
|
|
121
|
+
}
|
|
89
122
|
return result;
|
|
90
123
|
} catch (err) {
|
|
124
|
+
if (controller.signal.aborted) {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
91
127
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
92
|
-
|
|
128
|
+
if (mountedRef.current) {
|
|
129
|
+
setState({ loading: false, error });
|
|
130
|
+
}
|
|
93
131
|
throw error;
|
|
94
132
|
}
|
|
95
133
|
},
|
|
@@ -102,9 +140,25 @@ function useSendNui(eventName, options) {
|
|
|
102
140
|
loading: false,
|
|
103
141
|
error: null
|
|
104
142
|
});
|
|
143
|
+
const mountedRef = (0, import_react.useRef)(true);
|
|
144
|
+
const abortControllerRef = (0, import_react.useRef)(null);
|
|
145
|
+
(0, import_react.useEffect)(() => {
|
|
146
|
+
mountedRef.current = true;
|
|
147
|
+
return () => {
|
|
148
|
+
var _a;
|
|
149
|
+
mountedRef.current = false;
|
|
150
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
151
|
+
};
|
|
152
|
+
}, []);
|
|
105
153
|
const send = (0, import_react.useCallback)(
|
|
106
154
|
async (data) => {
|
|
107
|
-
|
|
155
|
+
var _a;
|
|
156
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
157
|
+
const controller = new AbortController();
|
|
158
|
+
abortControllerRef.current = controller;
|
|
159
|
+
if (mountedRef.current) {
|
|
160
|
+
setState({ loading: true, error: null });
|
|
161
|
+
}
|
|
108
162
|
try {
|
|
109
163
|
if (isEnvBrowser()) {
|
|
110
164
|
const delay = (options == null ? void 0 : options.mockDelay) ?? 500;
|
|
@@ -115,13 +169,19 @@ function useSendNui(eventName, options) {
|
|
|
115
169
|
headers: {
|
|
116
170
|
"Content-Type": "application/json; charset=UTF-8"
|
|
117
171
|
},
|
|
118
|
-
body: JSON.stringify(data ?? {})
|
|
172
|
+
body: JSON.stringify(data ?? {}),
|
|
173
|
+
signal: controller.signal
|
|
119
174
|
});
|
|
120
175
|
}
|
|
121
|
-
|
|
176
|
+
if (!controller.signal.aborted && mountedRef.current) {
|
|
177
|
+
setState({ loading: false, error: null });
|
|
178
|
+
}
|
|
122
179
|
} catch (err) {
|
|
180
|
+
if (controller.signal.aborted) return;
|
|
123
181
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
124
|
-
|
|
182
|
+
if (mountedRef.current) {
|
|
183
|
+
setState({ loading: false, error });
|
|
184
|
+
}
|
|
125
185
|
throw error;
|
|
126
186
|
}
|
|
127
187
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -39,9 +39,21 @@ async function fetchNui(eventName, data, options) {
|
|
|
39
39
|
headers: {
|
|
40
40
|
"Content-Type": "application/json; charset=UTF-8"
|
|
41
41
|
},
|
|
42
|
-
body: JSON.stringify(data ?? {})
|
|
42
|
+
body: JSON.stringify(data ?? {}),
|
|
43
|
+
signal: options == null ? void 0 : options.signal
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`NUI request failed: ${eventName} (${response.status})`);
|
|
47
|
+
}
|
|
48
|
+
const text = await response.text();
|
|
49
|
+
if (!text || text.length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
58
|
function useNuiCallback(eventName, callback, options) {
|
|
47
59
|
const [state, setState] = useState({
|
|
@@ -49,20 +61,46 @@ function useNuiCallback(eventName, callback, options) {
|
|
|
49
61
|
error: null
|
|
50
62
|
});
|
|
51
63
|
const callbackRef = useRef(callback);
|
|
64
|
+
const mountedRef = useRef(true);
|
|
65
|
+
const abortControllerRef = useRef(null);
|
|
52
66
|
useEffect(() => {
|
|
53
67
|
callbackRef.current = callback;
|
|
54
68
|
}, [callback]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
mountedRef.current = true;
|
|
71
|
+
return () => {
|
|
72
|
+
var _a;
|
|
73
|
+
mountedRef.current = false;
|
|
74
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
55
77
|
const fetch2 = useCallback(
|
|
56
78
|
async (data) => {
|
|
57
|
-
|
|
79
|
+
var _a;
|
|
80
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
abortControllerRef.current = controller;
|
|
83
|
+
if (mountedRef.current) {
|
|
84
|
+
setState({ loading: true, error: null });
|
|
85
|
+
}
|
|
58
86
|
try {
|
|
59
|
-
const result = await fetchNui(eventName, data,
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
const result = await fetchNui(eventName, data, {
|
|
88
|
+
...options,
|
|
89
|
+
signal: controller.signal
|
|
90
|
+
});
|
|
91
|
+
if (!controller.signal.aborted && mountedRef.current) {
|
|
92
|
+
callbackRef.current(result);
|
|
93
|
+
setState({ loading: false, error: null });
|
|
94
|
+
}
|
|
62
95
|
return result;
|
|
63
96
|
} catch (err) {
|
|
97
|
+
if (controller.signal.aborted) {
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
64
100
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
65
|
-
|
|
101
|
+
if (mountedRef.current) {
|
|
102
|
+
setState({ loading: false, error });
|
|
103
|
+
}
|
|
66
104
|
throw error;
|
|
67
105
|
}
|
|
68
106
|
},
|
|
@@ -75,9 +113,25 @@ function useSendNui(eventName, options) {
|
|
|
75
113
|
loading: false,
|
|
76
114
|
error: null
|
|
77
115
|
});
|
|
116
|
+
const mountedRef = useRef(true);
|
|
117
|
+
const abortControllerRef = useRef(null);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
mountedRef.current = true;
|
|
120
|
+
return () => {
|
|
121
|
+
var _a;
|
|
122
|
+
mountedRef.current = false;
|
|
123
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
124
|
+
};
|
|
125
|
+
}, []);
|
|
78
126
|
const send = useCallback(
|
|
79
127
|
async (data) => {
|
|
80
|
-
|
|
128
|
+
var _a;
|
|
129
|
+
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
abortControllerRef.current = controller;
|
|
132
|
+
if (mountedRef.current) {
|
|
133
|
+
setState({ loading: true, error: null });
|
|
134
|
+
}
|
|
81
135
|
try {
|
|
82
136
|
if (isEnvBrowser()) {
|
|
83
137
|
const delay = (options == null ? void 0 : options.mockDelay) ?? 500;
|
|
@@ -88,13 +142,19 @@ function useSendNui(eventName, options) {
|
|
|
88
142
|
headers: {
|
|
89
143
|
"Content-Type": "application/json; charset=UTF-8"
|
|
90
144
|
},
|
|
91
|
-
body: JSON.stringify(data ?? {})
|
|
145
|
+
body: JSON.stringify(data ?? {}),
|
|
146
|
+
signal: controller.signal
|
|
92
147
|
});
|
|
93
148
|
}
|
|
94
|
-
|
|
149
|
+
if (!controller.signal.aborted && mountedRef.current) {
|
|
150
|
+
setState({ loading: false, error: null });
|
|
151
|
+
}
|
|
95
152
|
} catch (err) {
|
|
153
|
+
if (controller.signal.aborted) return;
|
|
96
154
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
97
|
-
|
|
155
|
+
if (mountedRef.current) {
|
|
156
|
+
setState({ loading: false, error });
|
|
157
|
+
}
|
|
98
158
|
throw error;
|
|
99
159
|
}
|
|
100
160
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fivem-nui-react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "React hooks and utilities for FiveM NUI development",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/TGIANN/fivem-nui-react.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/TGIANN/fivem-nui-react#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/TGIANN/fivem-nui-react/issues"
|
|
12
|
+
},
|
|
5
13
|
"main": "dist/index.js",
|
|
6
14
|
"module": "dist/index.mjs",
|
|
7
15
|
"types": "dist/index.d.ts",
|
|
@@ -26,7 +34,7 @@
|
|
|
26
34
|
"hooks",
|
|
27
35
|
"cfx"
|
|
28
36
|
],
|
|
29
|
-
"author": "",
|
|
37
|
+
"author": "TGIANN (tgiann.com)",
|
|
30
38
|
"license": "MIT",
|
|
31
39
|
"type": "commonjs",
|
|
32
40
|
"peerDependencies": {
|