Where should you start when a NUI button stays loading?
Write the exact action and expected result before restarting anything. For example: opening a harmless settings panel should load its current display preference and enable the controls. Capture the resource version, reproduction steps and the visible state. Use a development server and test data, especially when the real action affects money, inventory or permissions.
This is evergreen guidance checked on 13 September 2026, with AI-assisted editorial authorship by FiveMCoach. It describes a debugging method, not a newly announced FiveM or framework fix. No FXServer session was run for this article; the small JavaScript helper below is tested separately from the game runtime.
Did the click reach the intended interface?
Separate an input problem from a pending request. Watch whether the browser click handler runs once. If it never runs, inspect the button's disabled state, overlays and the active NUI frame before changing the callback.
Cfx.re documents a focus stack for fullscreen NUI, with the most recently focused resource on top and no click-through across resource frames. It also documents CEF developer tools and the F8 command when developer mode is enabled. Read the fullscreen NUI reference.
During your own controlled test, open the intended frame and compare one click with one handler execution. If a local UI rerender accidentally adds a second listener, fix that lifecycle boundary before testing requests again. Treat this as a possible cause to investigate, not a diagnosis of every stuck interface.
Did the NUI request receive a response?
Compare the browser request's resource name and callback route with the registered handler. Cfx.re's example uses an HTTPS POST to the parent resource and JSON request data. The game-side callback response must be JSON-encodable and returned on every branch, including rejection. A missing response can stall and eventually fail the request. Check the official NUI callback contract.
Create a compact trace for your own code: click received, request dispatched, handler entered, reply produced, result rendered. Record only the boundary and a non-sensitive test reference. Do not log full player records, credentials or private payloads. The first missing boundary tells you where to inspect next; it does not identify the cause by itself.
How do you keep a valid rejection from looking like success?
Choose an application response contract before writing the success screen. For a harmless example, success can carry an explicit boolean and data; rejection can carry a false boolean plus a safe message. Unexpected payloads should produce a recoverable error, not a green confirmation.
This small browser-side classifier is our example contract, not a FiveM API. It assumes the JSON has already been parsed and does not validate a purchase, permission or server-side effect:
function replyState(reply) {
if (reply?.ok === true) {
return 'success';
}
if (reply?.ok === false) {
return 'rejected';
}
return 'invalid-reply';
}
The helper distinguishes an explicit true, an explicit false and anything else. Its six local checks cover true, false, null, an empty object, a string and a string-valued boolean. They test only this classifier. Your own interface still needs to display the safe message, restore controls appropriately and validate any expected data fields.
Which failures should the browser handle separately?
Fetch does not reject merely because a response has an HTTP error status. Inspect the response status, and handle JSON parsing errors separately from a valid application rejection. An AbortController can cancel the browser request, but that cancellation should not be presented as proof that a downstream operation was rolled back. Review MDN's fetch error and cancellation guidance.
In your UI, use distinct states for idle, submitting, success, rejected and failed. Keep the action label understandable while it is running. On a transport failure, show what the user can do next and preserve safe form input. Avoid swallowing an exception merely to remove the spinner.
If the operation changes persistent state, check the authoritative result before inviting another attempt. Disable repeated clicks while the same request is pending and implement the appropriate duplicate protection at the trusted boundary. A disabled button alone cannot prevent duplicate operations from other clients or retries.
What should a controlled recovery test include?
Test a harmless read or an isolated action first. Exercise an accepted payload, a deliberate application rejection, a missing or malformed reply, and a request that fails to complete within your chosen limit. Confirm that the visible state matches the observed outcome rather than the amount of time that passed.
Then test two rapid clicks, closing the panel while work is pending, reopening it, and stopping the test resource. The desired behavior depends on the action: a read may be safe to repeat, while a transaction needs reconciliation. Write down that rule before making Retry the default response to every error.
If a callback starts a separate server request, trace that boundary independently. A NUI acknowledgement is not sufficient evidence that the server accepted or committed the work. Use the event validation guide for the server-side review and the resource command guide when rehearsing resource lifecycle changes.
What evidence should you keep after the fix?
Save the reproduction steps, the boundary that failed, the correction and the expected versus observed result for each recovery case. Remove temporary verbose logging before release. Repeat the failing case on the intended supported setup; a passing helper test cannot prove that the game's NUI integration works.
For a manageable next milestone, use the free game plan and choose Developer with your current blocker. The developer path explains learning and coaching options if you want help understanding the result. Implementation work has a separate agreed scope.
