Using the ERR Boilerplate
This guide will walk you through how to use the ERR Boilerplate to create custom FiveM resources with modern web technologies.
Creating Custom UI Components
The boilerplate comes with Shadcn UI components that you can use to quickly build beautiful interfaces. Here's how to create a custom component:
Basic Component Structure
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
const MyComponent = () => {
return (
<Card className="w-full max-w-md mx-auto bg-card/80 backdrop-blur-sm border shadow-lg">
<CardHeader>
<CardTitle>My Custom Component</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground mb-4">
This is a custom component built with the ERR Boilerplate.
</p>
<Button>Click Me</Button>
</CardContent>
</Card>
);
};
export default MyComponent;
Communicating with the Game
Fetching Data from the Client
To fetch data from the client-side Lua script:
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { fetchNui } from '@/utils/fetchNui';
import LibIcon from '@/components/LibIcon';
const PlayerInfo = () => {
const [playerData, setPlayerData] = useState(null);
const [loading, setLoading] = useState(false);
const getPlayerData = async () => {
setLoading(true);
try {
const data = await fetchNui('getClientData');
setPlayerData(data);
} catch (error) {
console.error('Failed to fetch player data:', error);
} finally {
setLoading(false);
}
};
return (
<div className="p-4">
<Button
onClick={getPlayerData}
disabled={loading}
className="flex items-center gap-2"
>
<LibIcon icon="user" />
{loading ? 'Loading...' : 'Get Player Position'}
</Button>
{playerData && (
<div className="mt-4 p-3 bg-secondary/50 rounded-md">
<h3 className="font-medium mb-2">Player Position:</h3>
<p>X: {playerData.x.toFixed(2)}</p>
<p>Y: {playerData.y.toFixed(2)}</p>
<p>Z: {playerData.z.toFixed(2)}</p>
</div>
)}
</div>
);
};
export default PlayerInfo;
Listening for Events from the Client
To listen for events sent from the client:
import React, { useState } from 'react';
import { useNuiEvent } from '@/hooks/useNuiEvent';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import LibIcon from '@/components/LibIcon';
const Notifications = () => {
const [notifications, setNotifications] = useState([]);
useNuiEvent('showNotification', (data) => {
setNotifications((prev) => [...prev, data]);
// Auto-remove notification after 5 seconds
setTimeout(() => {
setNotifications((prev) => prev.filter((n) => n.id !== data.id));
}, 5000);
});
return (
<div className="fixed top-4 right-4 space-y-2 w-80">
{notifications.map((notification) => (
<Alert key={notification.id} className="bg-card/90 backdrop-blur-sm border shadow-md">
<LibIcon icon={notification.type === 'error' ? 'circle-exclamation' : 'circle-info'}
className={notification.type === 'error' ? 'text-destructive' : 'text-primary'} />
<AlertTitle>{notification.title}</AlertTitle>
<AlertDescription>{notification.message}</AlertDescription>
</Alert>
))}
</div>
);
};
export default Notifications;
Adding Custom Lua Functions
To extend the client-side functionality, add new functions to the client/main.lua
file:
-- Get player vehicle information
RegisterNUICallback('getVehicleData', function(_, cb)
local ped = PlayerPedId()
local vehicle = GetVehiclePedIsIn(ped, false)
if vehicle == 0 then
cb({ inVehicle = false })
return
end
local vehicleModel = GetEntityModel(vehicle)
local vehicleName = GetLabelText(GetDisplayNameFromVehicleModel(vehicleModel))
local vehicleSpeed = math.floor(GetEntitySpeed(vehicle) * 3.6) -- km/h
cb({
inVehicle = true,
name = vehicleName,
speed = vehicleSpeed,
health = math.floor((GetVehicleBodyHealth(vehicle) / 1000) * 100)
})
end)
Complete Examples
In this section, we'll walk through complete examples that demonstrate how to build real features with the ERR Boilerplate. These examples combine multiple concepts and show how everything works together.
Example 1: Vehicle HUD
Prerequisites
Before starting this example, follow these steps in order:
- First, ensure you have installed all dependencies in the web folder:
cd web
pnpm install
- Once the dependencies are installed, add the Progress component from Shadcn UI. This component will be used to display the vehicle's fuel level:
# Make sure you're in the web folder
pnpm dlx shadcn@latest add progress
💡 Tip: You can learn more about the Progress component in the Shadcn UI documentation (opens in a new tab). Understanding how it works will help you customize it for your needs.
Overview
In this example, we'll create a vehicle HUD that shows:
- Vehicle speed in km/h
- Fuel level with a progress bar
- Smooth animations for showing/hiding the HUD
The HUD will automatically appear when you enter a vehicle and disappear when you exit.
This example will teach you:
- How to monitor player state with Lua threads
- How to send data from Lua to React
- How to use Framer Motion for smooth animations
- How to use the Progress component for visual feedback
Step 1: Create the Lua Script
First, create a new file called vehicle.lua
in your client
folder. This script will:
- Check if the player is in a vehicle
- Send vehicle data (speed and fuel) to the UI
- Control when the HUD should be visible
Here's the complete code with explanations:
-- Track if the vehicle HUD is currently visible
local isInVehicleUiVisible = false
-- Create a loop that runs continuously to monitor vehicle state
CreateThread(function()
while true do
-- Get the player's character (ped)
local ped = PlayerPedId()
-- Check if the player is in any vehicle
local isInVehicle = IsPedInAnyVehicle(ped, false)
-- Only send UI visibility updates when the state changes
-- This prevents sending unnecessary messages to the UI
if isInVehicle ~= isInVehicleUiVisible then
isInVehicleUiVisible = isInVehicle
-- Tell the UI to show/hide the vehicle HUD
SendReactMessage('setVehicleUiVisible', isInVehicle)
end
-- If the player is in a vehicle, send vehicle data to the UI
if isInVehicle then
-- Get the vehicle the player is in
local vehicle = GetVehiclePedIsIn(ped, false)
-- Calculate speed in km/h (multiply by 3.6 to convert from m/s)
local vehicleSpeed = math.floor(GetEntitySpeed(vehicle) * 3.6)
-- Get the current fuel level
local fuel = GetVehicleFuelLevel(vehicle)
-- Send the vehicle data to our React UI
SendReactMessage('updateVehicleData', {
speed = vehicleSpeed,
fuel = fuel
})
end
-- Wait before the next update:
-- - 100ms when in vehicle (for smooth updates)
-- - 1000ms when not in vehicle (to save resources)
Wait(isInVehicle and 100 or 1000)
end
end)
Step 2: Create the React Component
Next, create a new file called VehicleHUD.tsx
in your src/components
folder. This component will:
- Listen for visibility changes from Lua
- Display vehicle speed and fuel level
- Animate smoothly when showing/hiding
Here's the complete code with explanations:
// Import required dependencies
import React, { useState } from 'react';
import { useNuiEvent } from '@/hooks/useNuiEvent';
import { Progress } from '@/components/ui/progress';
import LibIcon from '@/components/LibIcon';
import { motion, AnimatePresence } from 'framer-motion';
const VehicleHUD = () => {
// State for controlling visibility and vehicle data
const [visible, setVisible] = useState(false);
const [vehicleData, setVehicleData] = useState({ speed: 0, fuel: 100 });
// Listen for messages from our Lua script
useNuiEvent('setVehicleUiVisible', (isVisible) => {
setVisible(isVisible); // Show/hide the HUD
});
useNuiEvent('updateVehicleData', (data) => {
setVehicleData(data); // Update speed and fuel data
});
return (
// AnimatePresence enables exit animations
<AnimatePresence>
{visible && (
// Animated container with glass-like background
<motion.div
initial={{ opacity: 0, y: 50 }} // Start from below and fade in
animate={{ opacity: 1, y: 0 }} // Animate to final position
exit={{ opacity: 0, y: 50 }} // Fade out and slide down when hiding
className="fixed bottom-8 right-8 bg-card/80 p-4 rounded-lg border shadow-lg w-64"
>
{/* Speed display */}
<div className="flex justify-between items-center mb-4">
<h3 className="font-semibold text-lg">Vehicle Info</h3>
<div className="flex items-center gap-1">
<LibIcon icon="gauge-high" className="text-primary" />
<span className="text-xl font-bold">{vehicleData.speed}</span>
<span className="text-xs text-muted-foreground">km/h</span>
</div>
</div>
{/* Fuel gauge */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<LibIcon icon="gas-pump" className="text-amber-500" />
<div className="flex-1">
<Progress
value={vehicleData.fuel}
className="h-2"
/>
</div>
<span className="text-xs">{Math.round(vehicleData.fuel)}%</span>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
);
};
export default VehicleHUD;
💡 Tip: The component uses Tailwind CSS for styling. Classes like
bg-card/80
create a semi-transparent background, andshadow-lg
adds a nice shadow effect.
#### Step 3: Add the Component to App.tsx
Finally, import and add your VehicleHUD component to your main App component:
```tsx
import VehicleHUD from '@/components/VehicleHUD';
const App = () => {
// Existing code...
return (
<>
{/* Existing UI components */}
<VehicleHUD />
</>
);
};
Now when you enter a vehicle in-game, the HUD will automatically appear and display your speed and fuel level!
Using FontAwesome Icons
The boilerplate includes FontAwesome icons that you can use with the LibIcon
component:
import LibIcon from '@/components/LibIcon';
const IconExamples = () => {
return (
<div className="grid grid-cols-2 gap-4 p-4">
<div className="flex items-center gap-2">
<LibIcon icon="user" className="text-primary" />
<span>Regular Icon</span>
</div>
<div className="flex items-center gap-2">
<LibIcon icon="bell" animation="shake" className="text-amber-500" />
<span>Animated Icon (Shake)</span>
</div>
<div className="flex items-center gap-2">
<LibIcon icon="spinner" animation="spin" className="text-blue-500" />
<span>Animated Icon (Spin)</span>
</div>
<div className="flex items-center gap-2">
<LibIcon icon={['fab', 'discord']} className="text-indigo-500" />
<span>Brand Icon (Discord)</span>
</div>
</div>
);
};
Using Framer Motion for Animations
The boilerplate includes Framer Motion for creating smooth animations:
import { motion } from 'framer-motion';
const AnimatedCard = () => {
return (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3 }}
whileHover={{ scale: 1.02 }}
className="bg-card p-4 rounded-lg shadow-md"
>
<h3 className="font-medium mb-2">Animated Card</h3>
<p className="text-muted-foreground">This card animates on load and hover.</p>
</motion.div>
);
};
Best Practices
-
Performance Optimization
- Use React's
useMemo
anduseCallback
for expensive calculations or functions - Implement proper dependency arrays in
useEffect
hooks - Minimize state updates during animations
- Use React's
-
Error Handling
- Always wrap NUI calls in try/catch blocks
- Provide fallback UI for error states
- Use the built-in ErrorBoundary for catching React errors
-
Responsive Design
- Use TailwindCSS responsive modifiers (sm:, md:, lg:) for different screen sizes
- Test your UI at various resolutions
- Consider different aspect ratios common in FiveM servers
-
Code Organization
- Keep components small and focused on a single responsibility
- Use custom hooks to share logic between components
- Organize related files in appropriate directories