Installation
Ziggy includes a useRoute() hook that makes it easy to use the route() helper in your React components.
Basic Setup with @routes
If you’re using the @routes Blade directive, Ziggy’s config is available globally. Simply import and use the hook:
import React from 'react';
import { useRoute } from 'ziggy-js';
export default function PostsLink() {
const route = useRoute();
return <a href={route('posts.index')}>All Posts</a>;
}
Setup Without @routes
If you’re not using @routes, import Ziggy’s configuration and pass it to useRoute():
import React from 'react';
import { useRoute } from 'ziggy-js';
import { Ziggy } from './ziggy.js'; // Generated by php artisan ziggy:generate
export default function PostsLink() {
const route = useRoute(Ziggy);
return <a href={route('posts.index')}>All Posts</a>;
}
Making Config Global
To avoid passing Ziggy to every useRoute() call, make it available globally:
// app.js or index.js
import { Ziggy } from './ziggy.js';
// Make Ziggy globally available
globalThis.Ziggy = Ziggy;
Now you can use useRoute() without passing the config:
import { useRoute } from 'ziggy-js';
export default function Navigation() {
const route = useRoute(); // No config needed
return (
<nav>
<a href={route('home')}>Home</a>
<a href={route('about')}>About</a>
</nav>
);
}
Basic Usage
Simple Links
import React from 'react';
import { useRoute } from 'ziggy-js';
export default function Navigation() {
const route = useRoute();
return (
<nav className="main-nav">
<a href={route('home')} className="nav-link">Home</a>
<a href={route('posts.index')} className="nav-link">Blog</a>
<a href={route('contact')} className="nav-link">Contact</a>
</nav>
);
}
Links with Parameters
import React from 'react';
import { useRoute } from 'ziggy-js';
export default function PostCard({ post }) {
const route = useRoute();
return (
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<a href={route('posts.show', { post: post.id })}>
Read more →
</a>
</article>
);
}
Dynamic Lists
import React from 'react';
import { useRoute } from 'ziggy-js';
export default function PostList({ posts }) {
const route = useRoute();
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<a href={route('posts.show', { post: post })}>
{post.title}
</a>
</li>
))}
</ul>
);
}
React Router Integration
Use Ziggy with React Router for client-side navigation:
import React from 'react';
import { Link } from 'react-router-dom';
import { useRoute } from 'ziggy-js';
export default function Navigation() {
const route = useRoute();
return (
<nav>
<Link to={route('home', [], false)}>Home</Link>
<Link to={route('posts.index', [], false)}>Blog</Link>
<Link to={route('about', [], false)}>About</Link>
</nav>
);
}
Pass false as the third argument to route() to generate relative URLs without the domain.
POST Requests with axios
import React, { useState } from 'react';
import { useRoute } from 'ziggy-js';
import axios from 'axios';
export default function CreatePost() {
const route = useRoute();
const [post, setPost] = useState({ title: '', body: '' });
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await axios.post(route('posts.store'), post);
console.log('Post created:', response.data);
// Redirect to the new post
window.location.href = route('posts.show', { post: response.data.id });
} catch (error) {
console.error('Error creating post:', error);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={post.title}
onChange={(e) => setPost({ ...post, title: e.target.value })}
placeholder="Post title"
required
/>
<textarea
value={post.body}
onChange={(e) => setPost({ ...post, body: e.target.value })}
placeholder="Post content"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}
Update Requests
import React, { useState } from 'react';
import { useRoute } from 'ziggy-js';
import axios from 'axios';
export default function EditPost({ initialPost }) {
const route = useRoute();
const [post, setPost] = useState(initialPost);
const handleUpdate = async (e) => {
e.preventDefault();
await axios.put(
route('posts.update', { post: post.id }),
post
);
alert('Post updated successfully!');
};
const handleDelete = async () => {
if (!confirm('Are you sure?')) return;
await axios.delete(route('posts.destroy', { post: post.id }));
window.location.href = route('posts.index');
};
return (
<form onSubmit={handleUpdate}>
<input
type="text"
value={post.title}
onChange={(e) => setPost({ ...post, title: e.target.value })}
/>
<textarea
value={post.body}
onChange={(e) => setPost({ ...post, body: e.target.value })}
/>
<button type="submit">Update Post</button>
<button type="button" onClick={handleDelete}>
Delete Post
</button>
</form>
);
}
Data Fetching
Using fetch API
import React, { useState, useEffect } from 'react';
import { useRoute } from 'ziggy-js';
export default function Post({ postId }) {
const route = useRoute();
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchPost() {
try {
const response = await fetch(
route('api.posts.show', { post: postId })
);
const data = await response.json();
setPost(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchPost();
}, [postId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<article>
<h1>{post.title}</h1>
<div>{post.body}</div>
</article>
);
}
Custom Hook for Data Fetching
import { useState, useEffect } from 'react';
import { useRoute } from 'ziggy-js';
function useLaravelRoute(routeName, params) {
const route = useRoute();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(route(routeName, params));
const json = await response.json();
setData(json);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
}, [routeName, JSON.stringify(params)]);
return { data, loading, error };
}
// Usage
export default function Posts() {
const { data: posts, loading, error } = useLaravelRoute('api.posts.index');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error loading posts</div>;
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
TypeScript Support
Basic TypeScript Usage
import React from 'react';
import { useRoute } from 'ziggy-js';
import type { RouteUrl } from 'ziggy-js';
interface Post {
id: number;
title: string;
body: string;
}
interface PostCardProps {
post: Post;
}
export default function PostCard({ post }: PostCardProps) {
const route = useRoute();
const postUrl: RouteUrl = route('posts.show', { post: post.id });
return (
<article>
<h2>{post.title}</h2>
<a href={postUrl}>Read more</a>
</article>
);
}
With Generated Types
First, generate Ziggy’s TypeScript definitions:
php artisan ziggy:generate --types
Now you get full autocomplete for route names and parameters:
import React from 'react';
import { useRoute } from 'ziggy-js';
export default function Navigation() {
const route = useRoute();
// TypeScript knows 'posts.show' requires a 'post' parameter
const url = route('posts.show', { post: 1 }); // ✓ Valid
// TypeScript will error on invalid route names
// const invalid = route('invalid.route'); // ✗ Type error
return <a href={url}>View Post</a>;
}
Strict Type Checking
Enable strict route name checking:
// ziggy.d.ts
declare module 'ziggy-js' {
interface TypeConfig {
strictRouteNames: true;
}
}
export {};
Hook Implementation
The useRoute() hook is implemented as follows:
export function useRoute(defaultConfig) {
if (
!defaultConfig &&
!globalThis.Ziggy &&
typeof Ziggy === 'undefined' &&
!document.getElementById('ziggy-routes-json')
) {
throw new Error(
'Ziggy error: missing configuration. Ensure that a `Ziggy` variable is defined globally or pass a config object into the useRoute hook.',
);
}
return (name, params, absolute, config = defaultConfig) =>
route(name, params, absolute, config);
}
The hook returns a version of route() that automatically uses the config you passed to useRoute(), or falls back to the global Ziggy variable.
Server-Side Rendering (SSR)
For SSR frameworks like Next.js, ensure Ziggy’s config is available on both server and client:
// pages/_app.js
import { Ziggy } from '../ziggy.js';
if (typeof window === 'undefined') {
// Server-side
globalThis.Ziggy = Ziggy;
} else {
// Client-side
window.Ziggy = Ziggy;
}
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
Troubleshooting
”Missing configuration” error
This error means useRoute() can’t find Ziggy’s config. Either:
- Pass the config directly:
useRoute(Ziggy)
- Make it global:
globalThis.Ziggy = Ziggy
- Use the
@routes Blade directive
Routes not updating after changes
If you’re using ziggy:generate, re-run the command after changing routes:
php artisan ziggy:generate
TypeScript errors with route names
Generate type definitions:
php artisan ziggy:generate --types
Next Steps
TypeScript
Set up full type safety and autocompletion
Vue
Use Ziggy with Vue instead
Route Helper
Learn all route() function capabilities
JavaScript Frameworks
General framework integration guide