Monospace

useEvent

Preact hook for listening to NUI messages from FiveM Lua.

useEvent is a Preact hook that listens for window.postMessage events from the FiveM Lua client. It filters messages by a type field and calls your handler with the event data.

Import

import { useEvent } from '@repo/ui/utils/useEvent'

Signature

function useEvent<T = unknown>(
  type: string,
  handler: (data: T) => void
): void

Parameters

ParameterTypeDescription
typestringThe message type to listen for (matches the type field in SendNUIMessage)
handler(data: T) => voidCallback invoked when a matching message is received

Usage

import { useEvent } from '@repo/ui/utils/useEvent'

function Notification() {
  const [message, setMessage] = useState('')

  useEvent<string>('showNotification', (data) => {
    setMessage(data)
  })

  return <div>{message}</div>
}

Corresponding Lua Code

Send messages to the NUI from your Lua client:

SendNUIMessage({
  type = 'showNotification',
  data = 'Hello from Lua!'
})

Message Format

The hook expects messages in this format:

interface NuiMessageData<T> {
  type: string   // Matched against the `type` parameter
  data: T        // Passed to the handler
}

This matches FiveM's SendNUIMessage format, where you pass a table with type and data fields.

How It Works

  1. The hook stores the latest handler in a ref to avoid stale closures
  2. Attaches a message event listener to window
  3. Filters events by matching event.data.type with the provided type string
  4. Calls the handler with event.data.data when a match is found
  5. Cleans up the event listener on unmount

The handler ref is updated on every render, so the handler always has access to the latest component state without needing to re-register the event listener.

Source

packages/ui/src/utils/useEvent.ts
import { type RefObject, useEffect, useRef } from 'preact/compat'

interface NuiMessageData<T = unknown> {
  type: string
  data: T
}

type NuiHandlerSignature<T> = (data: T) => void

export const useEvent = <T = unknown>(
  type: string,
  handler: (data: T) => void
) => {
  const savedHandler: RefObject<NuiHandlerSignature<T>> = useRef(() => {})

  useEffect(() => {
    savedHandler.current = handler
  }, [handler])

  useEffect(() => {
    const eventListener = (event: MessageEvent<NuiMessageData<T>>) => {
      const { type: eventAction, data } = event.data
      if (savedHandler.current) {
        if (eventAction === type) {
          savedHandler.current(data)
        }
      }
    }

    window.addEventListener('message', eventListener)
    return () => window.removeEventListener('message', eventListener)
  }, [type])
}

On this page