Frontend Integration Examples¶
React with Apollo Client¶
import React, { useState } from 'react';
import { gql, useQuery } from '@apollo/client';
const GET_POSTS = gql`
query GetPosts(
$limit: Int!,
$offset: Int!,
$category: String,
$status: String
) {
allPosts(filter: {
category: { name: { exact: $category } }
status: { exact: $status }
}) {
results(
limit: $limit,
offset: $offset,
ordering: "-published_at"
) {
id
title
excerpt
publishedAt @date(format: "MMMM DD, YYYY")
viewCount @number(as: ",.0f")
author {
username
profile {
avatar
}
}
category {
name
slug
}
tags {
name
color
}
}
totalCount
}
}
`;
function PostsList() {
const [filters, setFilters] = useState({
search: '',
category: '',
status: 'published'
});
const [pagination, setPagination] = useState({
page: 0,
limit: 10
});
const { loading, error, data, refetch } = useQuery(GET_POSTS, {
variables: {
...filters,
limit: pagination.limit,
offset: pagination.page * pagination.limit
}
});
const handleSearch = (searchTerm) => {
setFilters(prev => ({ ...prev, search: searchTerm }));
setPagination(prev => ({ ...prev, page: 0 }));
};
const handlePageChange = (newPage) => {
setPagination(prev => ({ ...prev, page: newPage }));
};
if (loading) return <div>Loading posts...</div>;
if (error) return <div>Error: {error.message}</div>;
const posts = data?.allPosts?.results || [];
const totalCount = data?.allPosts?.totalCount || 0;
const totalPages = Math.ceil(totalCount / pagination.limit);
return (
<div className="posts-list">
{/* Search and Filters */}
<div className="filters">
<input
type="text"
placeholder="Search posts..."
value={filters.search}
onChange={(e) => handleSearch(e.target.value)}
/>
<select
value={filters.category}
onChange={(e) => setFilters(prev => ({
...prev,
category: e.target.value
}))}
>
<option value="">All Categories</option>
<option value="Technology">Technology</option>
<option value="Design">Design</option>
</select>
</div>
{/* Posts Grid */}
<div className="posts-grid">
{posts.map(post => (
<article key={post.id} className="post-card">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<div className="post-meta">
<span>By {post.author.username}</span>
<span>{post.publishedAt}</span>
<span>{post.viewCount} views</span>
</div>
<div className="post-tags">
{post.tags.map(tag => (
<span
key={tag.name}
style={{ color: tag.color }}
>
#{tag.name}
</span>
))}
</div>
</article>
))}
</div>
{/* Pagination */}
<div className="pagination">
<button
disabled={pagination.page === 0}
onClick={() => handlePageChange(pagination.page - 1)}
>
Previous
</button>
<span>
Page {pagination.page + 1} of {totalPages}
</span>
<button
disabled={pagination.page >= totalPages - 1}
onClick={() => handlePageChange(pagination.page + 1)}
>
Next
</button>
</div>
</div>
);
}
export default PostsList;
import React, { useState } from 'react';
import { gql, useMutation, useQuery } from '@apollo/client';
const CREATE_POST = gql`
mutation CreatePost($postData: PostInput!) {
createPost(newPost: $postData) {
ok
post {
id
title
slug
status
}
errors {
field
messages
}
}
}
`;
const GET_CATEGORIES_AND_TAGS = gql`
query GetCategoriesAndTags {
categories {
id
name
}
tags {
id
name
color
}
}
`;
function CreatePostForm() {
const [formData, setFormData] = useState({
title: '',
slug: '',
content: '',
excerpt: '',
status: 'draft',
category: '',
tags: []
});
const { data: optionsData } = useQuery(GET_CATEGORIES_AND_TAGS);
const [createPost, { loading, error }] = useMutation(CREATE_POST);
const handleSubmit = async (e) => {
e.preventDefault();
try {
const result = await createPost({
variables: { postData: formData }
});
if (result.data.createPost.ok) {
alert('Post created successfully!');
// Reset form or redirect
} else {
// Handle validation errors
console.error('Validation errors:', result.data.createPost.errors);
}
} catch (err) {
console.error('Mutation error:', err);
}
};
return (
<form onSubmit={handleSubmit} className="create-post-form">
<div className="form-group">
<label>Title</label>
<input
type="text"
value={formData.title}
onChange={(e) => setFormData(prev => ({
...prev,
title: e.target.value,
slug: e.target.value.toLowerCase().replace(/\s+/g, '-')
}))}
required
/>
</div>
<div className="form-group">
<label>Slug</label>
<input
type="text"
value={formData.slug}
onChange={(e) => setFormData(prev => ({
...prev,
slug: e.target.value
}))}
required
/>
</div>
<div className="form-group">
<label>Content</label>
<textarea
value={formData.content}
onChange={(e) => setFormData(prev => ({
...prev,
content: e.target.value
}))}
rows={10}
required
/>
</div>
<div className="form-group">
<label>Category</label>
<select
value={formData.category}
onChange={(e) => setFormData(prev => ({
...prev,
category: e.target.value
}))}
required
>
<option value="">Select Category</option>
{optionsData?.categories?.map(category => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
<div className="form-group">
<label>Status</label>
<select
value={formData.status}
onChange={(e) => setFormData(prev => ({
...prev,
status: e.target.value
}))}
>
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
</div>
<button
type="submit"
disabled={loading}
className="submit-button"
>
{loading ? 'Creating...' : 'Create Post'}
</button>
{error && (
<div className="error-message">
Error: {error.message}
</div>
)}
</form>
);
}
export default CreatePostForm;