React Examples ​
Complete examples for using Flowfull Clients with React applications.
Architecture
@pubflow/react- For authentication with Flowless (managed auth service)@pubflow/flowfull-client- For HTTP requests to your custom Flowfull backend
Installation ​
bash
# Install both packages
npm install @pubflow/react @pubflow/flowfull-clientBasic Setup ​
1. Configure PubflowProvider ​
typescript
// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PubflowProvider } from '@pubflow/react';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import Users from './pages/Users';
function App() {
return (
<PubflowProvider
config={{
baseUrl: import.meta.env.VITE_FLOWLESS_URL || 'https://your-flowless.pubflow.com',
bridgeBasePath: '/bridge',
authBasePath: '/auth'
}}
loginRedirectPath="/login"
publicPaths={['/login', '/register']}
>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/users" element={<Users />} />
</Routes>
</BrowserRouter>
</PubflowProvider>
);
}
export default App;2. Create Flowfull API Client ​
typescript
// src/api/flowfull.ts
import { createFlowfull } from '@pubflow/flowfull-client';
// Create client for your custom backend
export const flowfull = createFlowfull(
import.meta.env.VITE_BACKEND_URL || 'https://api.myapp.com',
{
headers: {
'X-Client-Version': '1.0.0'
},
retryAttempts: 3,
timeout: 30000
}
);Authentication with Flowless ​
Login Page ​
typescript
// src/pages/Login.tsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@pubflow/react';
function Login() {
const navigate = useNavigate();
const { login, isLoading } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError('');
try {
const result = await login({ email, password });
if (result.success) {
navigate('/dashboard');
} else {
setError(result.error || 'Login failed');
}
} catch (err) {
setError('An error occurred');
} finally {
setIsLoading(false);
}
}
return (
<div className="login-page">
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value.toLowerCase())}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login'}
</button>
{error && <div className="error">{error}</div>}
</form>
</div>
);
}
export default Login;User Profile Display ​
typescript
// src/pages/Dashboard.tsx
import { useAuth } from '@pubflow/react';
import { useNavigate } from 'react-router-dom';
function Dashboard() {
const { user, isAuthenticated, logout, isLoading } = useAuth();
const navigate = useNavigate();
async function handleLogout() {
await logout();
navigate('/login');
}
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
navigate('/login');
return null;
}
return (
<div className="dashboard">
<h1>Dashboard</h1>
<div className="user-info">
<p><strong>Name:</strong> {user?.name}</p>
<p><strong>Email:</strong> {user?.email}</p>
<p><strong>User Type:</strong> {user?.userType}</p>
</div>
<button onClick={handleLogout}>Logout</button>
</div>
);
}
export default Dashboard;Protected Route with useAuthGuard ​
typescript
// src/pages/AdminPanel.tsx
import { useAuthGuard } from '@pubflow/react';
import { useNavigate } from 'react-router-dom';
function AdminPanel() {
const navigate = useNavigate();
const { user, isLoading } = useAuthGuard({
allowedTypes: ['admin'],
onRedirect: (path, reason) => {
if (reason === 'unauthorized') {
alert('Admin access required');
}
navigate(path);
}
});
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Admin Panel</h1>
<p>Welcome, Admin {user?.name}!</p>
</div>
);
}
export default AdminPanel;Data Fetching from Custom Backend ​
Use @pubflow/flowfull-client to fetch data from your custom Flowfull backend:
User List with Pagination ​
typescript
// src/pages/Users.tsx
import { useState, useEffect } from 'react';
import { flowfull } from '../api/flowfull';
interface User {
id: string;
name: string;
email: string;
role: string;
}
function Users() {
const [users, setUsers] = useState<User[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const limit = 10;
useEffect(() => {
fetchUsers();
}, [page]);
async function fetchUsers() {
setIsLoading(true);
setError(null);
const response = await api
.query('/users')
.where('status', 'active')
.sort('name', 'asc')
.page(page)
.limit(limit)
.get<User[]>();
if (response.success) {
setUsers(response.data || []);
setTotal(response.meta?.total || 0);
} else {
setError(response.error || 'Failed to fetch users');
}
setIsLoading(false);
}
if (isLoading && users.length === 0) {
return <div>Loading users...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
const totalPages = Math.ceil(total / limit);
return (
<div>
<h1>Users</h1>
<button onClick={fetchUsers}>Refresh</button>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.user_type}</td>
</tr>
))}
</tbody>
</table>
<div className="pagination">
<button
onClick={() => setPage(p => p - 1)}
disabled={page === 1 || isLoading}
>
Previous
</button>
<span>Page {page} of {totalPages}</span>
<button
onClick={() => setPage(p => p + 1)}
disabled={page >= totalPages || isLoading}
>
Next
</button>
</div>
</div>
);
}
export default Users;Product Search with Filters ​
typescript
// src/pages/Products.tsx
import { useState, useEffect } from 'react';
import { api } from '../api/client';
import { gte, lte, inOp } from '@pubflow/flowfull-client';
interface Product {
id: string;
name: string;
price: number;
category: string;
stock: number;
}
function Products() {
const [products, setProducts] = useState<Product[]>([]);
const [search, setSearch] = useState('');
const [category, setCategory] = useState('');
const [minPrice, setMinPrice] = useState('');
const [maxPrice, setMaxPrice] = useState('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchProducts();
}, [search, category, minPrice, maxPrice]);
async function fetchProducts() {
setIsLoading(true);
let query = api.query('/products');
// Add search
if (search) {
query = query.search(search);
}
// Add category filter
if (category) {
query = query.where('category', category);
}
// Add price range
if (minPrice) {
query = query.where('price', gte(parseFloat(minPrice)));
}
if (maxPrice) {
query = query.where('price', lte(parseFloat(maxPrice)));
}
// Only in stock
query = query.where('stock', gte(1));
// Sort and paginate
query = query.sort('name', 'asc').limit(20);
const response = await query.get<Product[]>();
if (response.success) {
setProducts(response.data || []);
}
setIsLoading(false);
}
return (
<div>
<h1>Products</h1>
<div className="filters">
<input
type="text"
placeholder="Search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
<input
type="number"
placeholder="Min Price"
value={minPrice}
onChange={(e) => setMinPrice(e.target.value)}
/>
<input
type="number"
placeholder="Max Price"
value={maxPrice}
onChange={(e) => setMaxPrice(e.target.value)}
/>
</div>
{isLoading ? (
<div>Loading...</div>
) : (
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>${product.price}</p>
<p>Category: {product.category}</p>
<p>Stock: {product.stock}</p>
</div>
))}
</div>
)}
</div>
);
}
export default Products;Create/Update Form ​
typescript
// src/pages/CreateUser.tsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../api/client';
function CreateUser() {
const navigate = useNavigate();
const [formData, setFormData] = useState({
name: '',
email: '',
user_type: 'user'
});
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsLoading(true);
setError('');
const response = await api.post('/users', formData);
if (response.success) {
navigate('/users');
} else {
setError(response.error || 'Failed to create user');
}
setIsLoading(false);
}
return (
<div>
<h1>Create User</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value.toLowerCase() })}
required
/>
<select
value={formData.user_type}
onChange={(e) => setFormData({ ...formData, user_type: e.target.value })}
>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create User'}
</button>
{error && <div className="error">{error}</div>}
</form>
</div>
);
}
export default CreateUser;Using with React Query (Optional) ​
For advanced caching and state management, you can use React Query:
bash
npm install @tanstack/react-querytypescript
// src/hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../api/client';
interface User {
id: string;
name: string;
email: string;
}
export function useUsers(page = 1, limit = 10) {
return useQuery({
queryKey: ['users', page, limit],
queryFn: async () => {
const response = await api
.query('/users')
.page(page)
.limit(limit)
.get<User[]>();
if (!response.success) {
throw new Error(response.error);
}
return {
users: response.data,
meta: response.meta
};
}
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: Partial<User>) => {
const response = await api.post<User>('/users', data);
if (!response.success) {
throw new Error(response.error);
}
return response.data;
},
onSuccess: () => {
// Invalidate users query to refetch
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
}
// Usage in component
function UserList() {
const { data, isLoading, error } = useUsers(1, 10);
const createUser = useCreateUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data?.users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}Next Steps ​
- React Native Examples - Mobile examples
- Next.js Examples - SSR examples
- Tutorial - Complete tutorial
- API Reference - Full API documentation