fivem-nui-react 1.0.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 +143 -120
- package/dist/index.d.mts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +103 -9
- package/dist/index.mjs +101 -8
- 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,27 +10,46 @@ npm install fivem-nui-react
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **isEnvBrowser** - Check if running in browser (debug mode)
|
|
17
|
-
- **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) => {});
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
// Send request and get response
|
|
18
|
+
const data = await fetchNui("eventName", { payload });
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
// Hook with loading/error states and callback
|
|
21
|
+
const [fetch, { loading, error }] = useNuiCallback("eventName", (data) => {});
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Send data without expecting response
|
|
24
|
+
const [send, { loading, error }] = useSendNui("eventName");
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// Check if running in browser (debug mode)
|
|
27
|
+
if (isEnvBrowser())
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
```lua
|
|
31
|
+
-- Send message from FiveM client to UI
|
|
32
|
+
SendNUIMessage({ action = "eventName", data = { key = "value" } })
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
43
|
+
|
|
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
|
+
```
|
|
34
53
|
|
|
35
54
|
**Example:**
|
|
36
55
|
|
|
@@ -40,11 +59,6 @@ import { useNuiEvent } from "fivem-nui-react";
|
|
|
40
59
|
function App() {
|
|
41
60
|
const [visible, setVisible] = useState(false);
|
|
42
61
|
|
|
43
|
-
useNuiEvent<{ show: boolean }>("toggleUI", (data) => {
|
|
44
|
-
setVisible(data.show);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// With mock data for browser testing
|
|
48
62
|
useNuiEvent<{ show: boolean }>("toggleUI", (data) => setVisible(data.show), {
|
|
49
63
|
mockData: { show: true },
|
|
50
64
|
mockDelay: 1000,
|
|
@@ -54,7 +68,7 @@ function App() {
|
|
|
54
68
|
}
|
|
55
69
|
```
|
|
56
70
|
|
|
57
|
-
**
|
|
71
|
+
**FiveM Client (Lua):**
|
|
58
72
|
|
|
59
73
|
```lua
|
|
60
74
|
SendNUIMessage({
|
|
@@ -67,53 +81,53 @@ SendNUIMessage({
|
|
|
67
81
|
|
|
68
82
|
### fetchNui
|
|
69
83
|
|
|
70
|
-
Send a request to the
|
|
84
|
+
Send a request to the FiveM client and receive a response.
|
|
71
85
|
|
|
72
86
|
```tsx
|
|
73
|
-
fetchNui<T, D>(
|
|
87
|
+
fetchNui<T, D>(
|
|
88
|
+
eventName: string,
|
|
89
|
+
data?: D,
|
|
90
|
+
options?: { mockData?: T; mockDelay?: number; signal?: AbortSignal }
|
|
91
|
+
): Promise<T>
|
|
74
92
|
```
|
|
75
93
|
|
|
76
|
-
**Parameters:**
|
|
77
|
-
|
|
78
|
-
- `eventName` - The NUI callback event name
|
|
79
|
-
- `data` - Data to send to Lua
|
|
80
|
-
- `options` - Optional mock data configuration for browser testing
|
|
81
|
-
|
|
82
94
|
**Example:**
|
|
83
95
|
|
|
84
96
|
```tsx
|
|
85
97
|
import { fetchNui } from "fivem-nui-react";
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
const player = await fetchNui<PlayerData>(
|
|
100
|
+
"getPlayerData",
|
|
101
|
+
{ id: 1 },
|
|
102
|
+
{
|
|
103
|
+
mockData: { name: "John", level: 10 },
|
|
104
|
+
mockDelay: 500,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
```
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
const player = await fetchNui<PlayerData>("getPlayerData", { id: 1 });
|
|
94
|
-
console.log(player.name, player.level);
|
|
95
|
-
}
|
|
109
|
+
**With AbortController:**
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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();
|
|
109
124
|
```
|
|
110
125
|
|
|
111
|
-
**Lua
|
|
126
|
+
**FiveM Client (Lua):**
|
|
112
127
|
|
|
113
128
|
```lua
|
|
114
129
|
RegisterNUICallback("getPlayerData", function(data, cb)
|
|
115
130
|
local playerId = data.id
|
|
116
|
-
-- Fetch player data...
|
|
117
131
|
cb({ name = "John", level = 10 })
|
|
118
132
|
end)
|
|
119
133
|
```
|
|
@@ -122,48 +136,28 @@ end)
|
|
|
122
136
|
|
|
123
137
|
### useNuiCallback
|
|
124
138
|
|
|
125
|
-
|
|
139
|
+
Hook for NUI requests with loading/error states. Calls the callback with response data.
|
|
126
140
|
|
|
127
141
|
```tsx
|
|
128
142
|
useNuiCallback<T, D>(
|
|
129
143
|
eventName: string,
|
|
130
144
|
callback: (data: T) => void,
|
|
131
|
-
options?:
|
|
132
|
-
): [
|
|
145
|
+
options?: { mockData?: T; mockDelay?: number }
|
|
146
|
+
): [(data?: D) => Promise<T>, { loading: boolean; error: Error | null }]
|
|
133
147
|
```
|
|
134
148
|
|
|
135
|
-
**Parameters:**
|
|
136
|
-
|
|
137
|
-
- `eventName` - The NUI callback event name
|
|
138
|
-
- `callback` - Callback function executed when response is received
|
|
139
|
-
- `options` - Optional mock data configuration for browser testing
|
|
140
|
-
|
|
141
|
-
**Returns:**
|
|
142
|
-
|
|
143
|
-
- `fetchFn` - Function to trigger the request
|
|
144
|
-
- `state.loading` - Boolean indicating if request is in progress
|
|
145
|
-
- `state.error` - Error object if request failed
|
|
146
|
-
|
|
147
149
|
**Example:**
|
|
148
150
|
|
|
149
151
|
```tsx
|
|
150
152
|
import { useNuiCallback } from "fivem-nui-react";
|
|
151
153
|
|
|
152
|
-
interface PlayerData {
|
|
153
|
-
name: string;
|
|
154
|
-
level: number;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
154
|
function PlayerInfo() {
|
|
158
155
|
const [player, setPlayer] = useState<PlayerData | null>(null);
|
|
159
156
|
|
|
160
157
|
const [fetchPlayer, { loading, error }] = useNuiCallback<PlayerData>(
|
|
161
158
|
"getPlayerData",
|
|
162
159
|
(data) => setPlayer(data),
|
|
163
|
-
{
|
|
164
|
-
mockData: { name: "John", level: 10 },
|
|
165
|
-
mockDelay: 500,
|
|
166
|
-
},
|
|
160
|
+
{ mockData: { name: "John", level: 10 }, mockDelay: 500 },
|
|
167
161
|
);
|
|
168
162
|
|
|
169
163
|
useEffect(() => {
|
|
@@ -172,96 +166,125 @@ function PlayerInfo() {
|
|
|
172
166
|
|
|
173
167
|
if (loading) return <div>Loading...</div>;
|
|
174
168
|
if (error) return <div>Error: {error.message}</div>;
|
|
175
|
-
if (!player) return null;
|
|
176
169
|
|
|
177
|
-
return
|
|
178
|
-
<div>
|
|
179
|
-
<p>Name: {player.name}</p>
|
|
180
|
-
<p>Level: {player.level}</p>
|
|
181
|
-
</div>
|
|
182
|
-
);
|
|
170
|
+
return <div>{player?.name}</div>;
|
|
183
171
|
}
|
|
184
172
|
```
|
|
185
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
|
+
|
|
186
183
|
---
|
|
187
184
|
|
|
188
|
-
###
|
|
185
|
+
### useSendNui
|
|
189
186
|
|
|
190
|
-
|
|
187
|
+
Hook for sending data to FiveM client without expecting a response.
|
|
191
188
|
|
|
192
189
|
```tsx
|
|
193
|
-
|
|
190
|
+
useSendNui<D>(
|
|
191
|
+
eventName: string,
|
|
192
|
+
options?: { mockDelay?: number }
|
|
193
|
+
): [(data?: D) => Promise<void>, { loading: boolean; error: Error | null }]
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
**Example:**
|
|
197
197
|
|
|
198
|
+
```tsx
|
|
199
|
+
import { useSendNui } from "fivem-nui-react";
|
|
200
|
+
|
|
201
|
+
function CloseButton() {
|
|
202
|
+
const [closeUI, { loading }] = useSendNui<{ reason: string }>("closeUI");
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<button
|
|
206
|
+
onClick={() => closeUI({ reason: "user_clicked" })}
|
|
207
|
+
disabled={loading}
|
|
208
|
+
>
|
|
209
|
+
Close
|
|
210
|
+
</button>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**FiveM Client (Lua):**
|
|
216
|
+
|
|
217
|
+
```lua
|
|
218
|
+
RegisterNUICallback("closeUI", function(data, cb)
|
|
219
|
+
SetNuiFocus(false, false)
|
|
220
|
+
cb("ok")
|
|
221
|
+
end)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### isEnvBrowser
|
|
227
|
+
|
|
228
|
+
Check if running in browser (outside FiveM).
|
|
229
|
+
|
|
198
230
|
```tsx
|
|
199
231
|
import { isEnvBrowser } from "fivem-nui-react";
|
|
200
232
|
|
|
201
233
|
if (isEnvBrowser()) {
|
|
202
234
|
console.log("Running in browser - debug mode");
|
|
203
|
-
} else {
|
|
204
|
-
console.log("Running in FiveM");
|
|
205
235
|
}
|
|
206
236
|
```
|
|
207
237
|
|
|
208
238
|
---
|
|
209
239
|
|
|
210
|
-
## Browser Testing
|
|
240
|
+
## Browser Testing
|
|
211
241
|
|
|
212
|
-
All hooks
|
|
242
|
+
All hooks support mock data for testing in browser without FiveM:
|
|
213
243
|
|
|
214
|
-
|
|
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) | - |
|
|
215
249
|
|
|
216
|
-
|
|
217
|
-
- `fetchNui` will return `mockData` after `mockDelay` milliseconds
|
|
218
|
-
- `useNuiCallback` will call the callback with `mockData` after `mockDelay` milliseconds
|
|
250
|
+
When `isEnvBrowser()` is `true`:
|
|
219
251
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
- `
|
|
223
|
-
- `
|
|
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
|
|
224
256
|
|
|
225
257
|
---
|
|
226
258
|
|
|
227
|
-
## TypeScript
|
|
259
|
+
## TypeScript
|
|
228
260
|
|
|
229
|
-
All functions are fully typed
|
|
261
|
+
All functions are fully typed:
|
|
230
262
|
|
|
231
263
|
```tsx
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
title: string;
|
|
264
|
+
interface PlayerData {
|
|
265
|
+
name: string;
|
|
266
|
+
level: number;
|
|
236
267
|
}
|
|
237
268
|
|
|
238
269
|
interface PlayerRequest {
|
|
239
270
|
id: number;
|
|
240
271
|
}
|
|
241
272
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
level: number;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Use with type parameters
|
|
248
|
-
useNuiEvent<ShowUIData>("showUI", (data) => {
|
|
249
|
-
// data is typed as ShowUIData
|
|
250
|
-
console.log(data.visible, data.title);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const player = await fetchNui<PlayerResponse, PlayerRequest>("getPlayer", {
|
|
273
|
+
// Typed response
|
|
274
|
+
const player = await fetchNui<PlayerData, PlayerRequest>("getPlayer", {
|
|
254
275
|
id: 1,
|
|
255
276
|
});
|
|
256
|
-
// player is typed as PlayerResponse
|
|
257
277
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
278
|
+
// Typed callback
|
|
279
|
+
const [fetchPlayer, { loading }] = useNuiCallback<PlayerData, PlayerRequest>(
|
|
280
|
+
"getPlayer",
|
|
281
|
+
(data) => console.log(data.name),
|
|
282
|
+
);
|
|
262
283
|
```
|
|
263
284
|
|
|
264
|
-
|
|
285
|
+
## Note
|
|
286
|
+
|
|
287
|
+
Most of the code in this project was written by AI and then manually reviewed and edited.
|
|
265
288
|
|
|
266
289
|
## License
|
|
267
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 }, {
|
|
@@ -70,5 +71,26 @@ type UseNuiCallbackReturn<T, D> = [
|
|
|
70
71
|
* }, [fetchPlayer]);
|
|
71
72
|
*/
|
|
72
73
|
declare function useNuiCallback<T = unknown, D = unknown>(eventName: string, callback: (data: T) => void, options?: UseNuiCallbackOptions<T>): UseNuiCallbackReturn<T, D>;
|
|
74
|
+
interface UseSendNuiOptions {
|
|
75
|
+
mockDelay?: number;
|
|
76
|
+
}
|
|
77
|
+
interface UseSendNuiState {
|
|
78
|
+
loading: boolean;
|
|
79
|
+
error: Error | null;
|
|
80
|
+
}
|
|
81
|
+
type UseSendNuiReturn<D> = [(data?: D) => Promise<void>, UseSendNuiState];
|
|
82
|
+
/**
|
|
83
|
+
* React hook for sending data to NUI callback without expecting a response
|
|
84
|
+
* @param eventName - The callback event name
|
|
85
|
+
* @param options - Options for mock delay in browser mode
|
|
86
|
+
* @returns [sendFunction, { loading, error }]
|
|
87
|
+
* @example
|
|
88
|
+
* const [closeUI, { loading }] = useSendNui("closeUI");
|
|
89
|
+
*
|
|
90
|
+
* const handleClose = () => {
|
|
91
|
+
* closeUI({ reason: "user_clicked" });
|
|
92
|
+
* };
|
|
93
|
+
*/
|
|
94
|
+
declare function useSendNui<D = unknown>(eventName: string, options?: UseSendNuiOptions): UseSendNuiReturn<D>;
|
|
73
95
|
|
|
74
|
-
export { type FetchNuiOptions, type NuiEventHandler, type NuiMessageEvent, type UseNuiCallbackOptions, type UseNuiCallbackReturn, type UseNuiCallbackState, type UseNuiEventOptions, fetchNui, isEnvBrowser, useNuiCallback, useNuiEvent };
|
|
96
|
+
export { type FetchNuiOptions, type NuiEventHandler, type NuiMessageEvent, type UseNuiCallbackOptions, type UseNuiCallbackReturn, type UseNuiCallbackState, type UseNuiEventOptions, type UseSendNuiOptions, type UseSendNuiReturn, type UseSendNuiState, fetchNui, isEnvBrowser, useNuiCallback, useNuiEvent, useSendNui };
|
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 }, {
|
|
@@ -70,5 +71,26 @@ type UseNuiCallbackReturn<T, D> = [
|
|
|
70
71
|
* }, [fetchPlayer]);
|
|
71
72
|
*/
|
|
72
73
|
declare function useNuiCallback<T = unknown, D = unknown>(eventName: string, callback: (data: T) => void, options?: UseNuiCallbackOptions<T>): UseNuiCallbackReturn<T, D>;
|
|
74
|
+
interface UseSendNuiOptions {
|
|
75
|
+
mockDelay?: number;
|
|
76
|
+
}
|
|
77
|
+
interface UseSendNuiState {
|
|
78
|
+
loading: boolean;
|
|
79
|
+
error: Error | null;
|
|
80
|
+
}
|
|
81
|
+
type UseSendNuiReturn<D> = [(data?: D) => Promise<void>, UseSendNuiState];
|
|
82
|
+
/**
|
|
83
|
+
* React hook for sending data to NUI callback without expecting a response
|
|
84
|
+
* @param eventName - The callback event name
|
|
85
|
+
* @param options - Options for mock delay in browser mode
|
|
86
|
+
* @returns [sendFunction, { loading, error }]
|
|
87
|
+
* @example
|
|
88
|
+
* const [closeUI, { loading }] = useSendNui("closeUI");
|
|
89
|
+
*
|
|
90
|
+
* const handleClose = () => {
|
|
91
|
+
* closeUI({ reason: "user_clicked" });
|
|
92
|
+
* };
|
|
93
|
+
*/
|
|
94
|
+
declare function useSendNui<D = unknown>(eventName: string, options?: UseSendNuiOptions): UseSendNuiReturn<D>;
|
|
73
95
|
|
|
74
|
-
export { type FetchNuiOptions, type NuiEventHandler, type NuiMessageEvent, type UseNuiCallbackOptions, type UseNuiCallbackReturn, type UseNuiCallbackState, type UseNuiEventOptions, fetchNui, isEnvBrowser, useNuiCallback, useNuiEvent };
|
|
96
|
+
export { type FetchNuiOptions, type NuiEventHandler, type NuiMessageEvent, type UseNuiCallbackOptions, type UseNuiCallbackReturn, type UseNuiCallbackState, type UseNuiEventOptions, type UseSendNuiOptions, type UseSendNuiReturn, type UseSendNuiState, fetchNui, isEnvBrowser, useNuiCallback, useNuiEvent, useSendNui };
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,8 @@ __export(index_exports, {
|
|
|
22
22
|
fetchNui: () => fetchNui,
|
|
23
23
|
isEnvBrowser: () => isEnvBrowser,
|
|
24
24
|
useNuiCallback: () => useNuiCallback,
|
|
25
|
-
useNuiEvent: () => useNuiEvent
|
|
25
|
+
useNuiEvent: () => useNuiEvent,
|
|
26
|
+
useSendNui: () => useSendNui
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
28
29
|
var import_react = require("react");
|
|
@@ -65,9 +66,21 @@ async function fetchNui(eventName, data, options) {
|
|
|
65
66
|
headers: {
|
|
66
67
|
"Content-Type": "application/json; charset=UTF-8"
|
|
67
68
|
},
|
|
68
|
-
body: JSON.stringify(data ?? {})
|
|
69
|
+
body: JSON.stringify(data ?? {}),
|
|
70
|
+
signal: options == null ? void 0 : options.signal
|
|
69
71
|
});
|
|
70
|
-
|
|
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
|
+
}
|
|
71
84
|
}
|
|
72
85
|
function useNuiCallback(eventName, callback, options) {
|
|
73
86
|
const [state, setState] = (0, import_react.useState)({
|
|
@@ -75,20 +88,46 @@ function useNuiCallback(eventName, callback, options) {
|
|
|
75
88
|
error: null
|
|
76
89
|
});
|
|
77
90
|
const callbackRef = (0, import_react.useRef)(callback);
|
|
91
|
+
const mountedRef = (0, import_react.useRef)(true);
|
|
92
|
+
const abortControllerRef = (0, import_react.useRef)(null);
|
|
78
93
|
(0, import_react.useEffect)(() => {
|
|
79
94
|
callbackRef.current = callback;
|
|
80
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
|
+
}, []);
|
|
81
104
|
const fetch2 = (0, import_react.useCallback)(
|
|
82
105
|
async (data) => {
|
|
83
|
-
|
|
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
|
+
}
|
|
84
113
|
try {
|
|
85
|
-
const result = await fetchNui(eventName, data,
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
}
|
|
88
122
|
return result;
|
|
89
123
|
} catch (err) {
|
|
124
|
+
if (controller.signal.aborted) {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
90
127
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
91
|
-
|
|
128
|
+
if (mountedRef.current) {
|
|
129
|
+
setState({ loading: false, error });
|
|
130
|
+
}
|
|
92
131
|
throw error;
|
|
93
132
|
}
|
|
94
133
|
},
|
|
@@ -96,10 +135,65 @@ function useNuiCallback(eventName, callback, options) {
|
|
|
96
135
|
);
|
|
97
136
|
return [fetch2, state];
|
|
98
137
|
}
|
|
138
|
+
function useSendNui(eventName, options) {
|
|
139
|
+
const [state, setState] = (0, import_react.useState)({
|
|
140
|
+
loading: false,
|
|
141
|
+
error: null
|
|
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
|
+
}, []);
|
|
153
|
+
const send = (0, import_react.useCallback)(
|
|
154
|
+
async (data) => {
|
|
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
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
if (isEnvBrowser()) {
|
|
164
|
+
const delay = (options == null ? void 0 : options.mockDelay) ?? 500;
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
166
|
+
} else {
|
|
167
|
+
await fetch(`https://${resourceName}/${eventName}`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/json; charset=UTF-8"
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify(data ?? {}),
|
|
173
|
+
signal: controller.signal
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!controller.signal.aborted && mountedRef.current) {
|
|
177
|
+
setState({ loading: false, error: null });
|
|
178
|
+
}
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (controller.signal.aborted) return;
|
|
181
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
182
|
+
if (mountedRef.current) {
|
|
183
|
+
setState({ loading: false, error });
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[eventName, options]
|
|
189
|
+
);
|
|
190
|
+
return [send, state];
|
|
191
|
+
}
|
|
99
192
|
// Annotate the CommonJS export names for ESM import in node:
|
|
100
193
|
0 && (module.exports = {
|
|
101
194
|
fetchNui,
|
|
102
195
|
isEnvBrowser,
|
|
103
196
|
useNuiCallback,
|
|
104
|
-
useNuiEvent
|
|
197
|
+
useNuiEvent,
|
|
198
|
+
useSendNui
|
|
105
199
|
});
|
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
|
},
|
|
@@ -70,9 +108,64 @@ function useNuiCallback(eventName, callback, options) {
|
|
|
70
108
|
);
|
|
71
109
|
return [fetch2, state];
|
|
72
110
|
}
|
|
111
|
+
function useSendNui(eventName, options) {
|
|
112
|
+
const [state, setState] = useState({
|
|
113
|
+
loading: false,
|
|
114
|
+
error: null
|
|
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
|
+
}, []);
|
|
126
|
+
const send = useCallback(
|
|
127
|
+
async (data) => {
|
|
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
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
if (isEnvBrowser()) {
|
|
137
|
+
const delay = (options == null ? void 0 : options.mockDelay) ?? 500;
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
139
|
+
} else {
|
|
140
|
+
await fetch(`https://${resourceName}/${eventName}`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json; charset=UTF-8"
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify(data ?? {}),
|
|
146
|
+
signal: controller.signal
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (!controller.signal.aborted && mountedRef.current) {
|
|
150
|
+
setState({ loading: false, error: null });
|
|
151
|
+
}
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (controller.signal.aborted) return;
|
|
154
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
155
|
+
if (mountedRef.current) {
|
|
156
|
+
setState({ loading: false, error });
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[eventName, options]
|
|
162
|
+
);
|
|
163
|
+
return [send, state];
|
|
164
|
+
}
|
|
73
165
|
export {
|
|
74
166
|
fetchNui,
|
|
75
167
|
isEnvBrowser,
|
|
76
168
|
useNuiCallback,
|
|
77
|
-
useNuiEvent
|
|
169
|
+
useNuiEvent,
|
|
170
|
+
useSendNui
|
|
78
171
|
};
|
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": {
|