Quick Start
Learn how to install and configure the @query-api/react package.
Install
npm install @query-api/react
Requirements
Before you start be sure to have the following prerequisites to follow this guide:
- Craft CMS + Query API successfully installed and running
- A Craft CMS section with at least one entry type (I used sections called
home
andnews
with entry typeshome
) - A React application set up with TypeScript (e.g.,
npm create vite@latest react-demo -- --template react-ts
)
If you prefer to dive straight into code or need a reference, check out the demo project.
Connect to Craft CMS
To use the Query API in your React application, you need to initialize the client with your Craft CMS backend details and map your React components to Craft Entries.
You can do this in your main entry file, typically main.tsx
or index.tsx
.
Here's an example of how to set it up:
import { StrictMode } from 'react'
import { BrowserRouter } from 'react-router'
import * as ReactDOM from 'react-dom/client'
import App from './app/app'
// craftInit is the main function to initialize the Query API client.
// CraftNotImplemented is a placeholder for components that are not implemented yet.
import { craftInit, CraftNotImplemented } from '@query-api/react'
// Import your React components that will be used in your Craft CMS Entries.
import Home from './app/views/Home'
import News from './app/views/News'
import Headline from './app/components/Headline'
craftInit({
baseUrl: 'https://your-craft-backend.ddev.site',
authToken: 'Bearer yourBearerToken',
contentMapping: {
pages: {
home: Home, // Maps section home entry with entry type home to the Home component.
'news:home': News, // Maps section news entry with entry type home to the News component.
},
components: {
headline: Headline, // Entry type headline will be rendered with the Headline component.
imageText: CraftNotImplemented,
},
},
})
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
)
To find more about the configuration options, check out the Configuration Docs.
A few words about contentMapping:
This tells the Query API which React components to use for each Craft CMS section or entry type.
In pages
, map sections and entry types to React pages using sectionHandle:entryTypeHandle
(e.g., news:home
). If the entry type matches the section handle or is default
, just use the section handle (e.g., home
).
In components
, map entry types to React components—useful for matrix blocks.
Display Page
To display the content from Craft CMS, you first need to fetch the data and then you can use the CraftPage
component.
This component will automatically render the pages based on the contentMapping configuration.
Add Fetch Composable
To fetch data from Craft CMS, create a useCraftFetch.ts
file in your composables
directory.
This will be a custom hook that uses the Fetch API to retrieve data from your Craft CMS backend.
If you are using Tanstack Query, you can use the useQuery
hook instead.
import { useEffect, useState } from 'react'
interface UseCraftFetchResult<T> {
data: T | null
loading: boolean
error: string | null
}
export function useCraftFetch<T = object>(url: string | null, authToken?: string): UseCraftFetchResult<T> {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!url || !authToken) {
throw new Error("Please provide a valid url and auth token")
}
setLoading(true)
setError(null)
fetch(url, {
headers: {
Authorization: authToken
}
}).then(async (res) => {
if (!res.ok) throw new Error(await res.text())
return res.json()
})
.then(setData)
.catch((err) => setError(err.message))
.finally(() => setLoading(false))
}, [url, authToken])
return { data, loading, error }
}
export default useCraftFetch
Configure Routing
Next, create a CraftRouter.tsx
file in your app
directory. This file will handle the routing and rendering of the pages based on the data fetched from Craft CMS.
import { useParams } from 'react-router'
import { CraftPage, getCraftInstance, createCraftUrl } from '@query-api/react'
import { useCraftFetch } from './composables/useCraftFetch'
import type { CraftPageBase } from '../types'
export default function CraftRouter() {
const { '*': params } = useParams()
const uri = params !== '' ? params : '__home__'
const { authToken } = getCraftInstance()
const apiUrl = createCraftUrl('entries').uri(uri).buildUrl('one')
const { data, loading, error } = useCraftFetch<CraftPageBase>(apiUrl, authToken)
if (error) {
console.error(error)
}
return (
<div>
{!loading && data && <CraftPage content={data} />}
</div>
)
}
Now you can use the CraftRouter
component in your main app.tsx
file to handle the routing and rendering of the pages.
For me it looks like this:
import { Route, Routes, Link } from 'react-router'
import CraftRouter from './CraftRouter'
export function App() {
return (
<div>
<nav role="navigation">
<Link to="/">Home</Link>
<Link to="/news-article-1">News Article 1</Link>
</nav>
<Routes>
<Route path="*" element={<CraftRouter />} /> // Catch-all route to handle all paths
</Routes>
</div>
)
}
export default App
You can now navigate to different pages in your Craft CMS site, and the CraftRouter
should fetch the data and render the appropriate page using the CraftPage
component.
The data of the fetch will be passed as prop to the mapped React component.
To display the data, you simply use this code:
import type { CraftPageHome } from '../../types'
export default function News(props: CraftPageHome) {
return (
<pre>
{JSON.stringify(props, null, 2)}
</pre>
)
}
That CraftPageHome
type is a generated type that represents the data structure of the home
entry type in your Craft CMS.
You can read more about how to generate these types in the Query API Commands Docs.
Display Components
Let's say you have a matrix block with the handle contentBuilder
in your home
entry type. You can render these blocks using the CraftArea
component.
This component will dynamically render React components based on the content provided from Craft CMS.
import type { CraftPageHome } from '../../types'
export default function Home(props: CraftPageHome) {
return (
<div>
<h1>{props.title}</h1>
<CraftArea content={props.contentBuilder} />
</div>
)
}
Standalone Queries
If you want to fetch data from Craft CMS without using the CraftPage
component, you can use the useCraftFetch
hook directly in your components.
Let's say you want to show a list of news articles on your homepage. You can do this by fetching the data directly from the Craft CMS API.
import type { CraftPageHome } from '../../types'
import { CraftArea, getCraftInstance, createCraftUrl } from '@query-api/react'
import { useCraftFetch } from '../composables/useCraftFetch'
export default function Home(props: CraftPageHome) {
const {authToken} = getCraftInstance()
const url = createCraftUrl('entries').section('news').limit(3).fields(['title']).buildUrl('all')
const {data: news, loading, error} = useCraftFetch<CraftPageHome[]>(url, authToken)
if (error) {
console.error(error)
}
return (
<div>
<h1>{props.title}</h1>
<CraftArea content={props.contentBuilder} />
<h2>Related News</h2>
{!loading && news && (
<ul>
{news.map((item, idx) => (
<li key={idx}>
<a href={item.metadata.url}>{item.title}</a>
</li>
))}
</ul>
)}
</div>
)
}
Anything missing?
If you have any questions, run into issues, or have ideas for improvements, your feedback is very welcome! Please don't hesitate to open an issue on GitHub. Whether it's a bug report, feature request, or general suggestion, your input helps make this project better for everyone.