Skip to content

React Examples ​

Complete examples for using Flowfull Clients with React.

Basic Setup ​

App Setup with Provider ​

typescript
// App.tsx
import { PubflowProvider } from '@pubflow/react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
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_BACKEND_URL,
        bridgeBasePath: '/bridge',
        authBasePath: '/auth'
      }}
    >
      <BrowserRouter>
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/users" element={<Users />} />
        </Routes>
      </BrowserRouter>
    </PubflowProvider>
  );
}

export default App;

Authentication ​

Login Page ​

typescript
// pages/Login.tsx
import { useState } from 'react';
import { useAuth } from '@pubflow/react';
import { useNavigate } from 'react-router-dom';

function Login() {
  const { login, isLoading, isAuthenticated } = useAuth();
  const navigate = useNavigate();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  // Redirect if already authenticated
  if (isAuthenticated) {
    navigate('/dashboard');
    return null;
  }

  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');
    }
  }

  return (
    <div className="login-page">
      <h1>Login</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          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;

Protected Route ​

typescript
// components/ProtectedRoute.tsx
import { useAuth } from '@pubflow/react';
import { Navigate } from 'react-router-dom';

interface ProtectedRouteProps {
  children: React.ReactNode;
  allowedTypes?: string[];
}

function ProtectedRoute({ children, allowedTypes }: ProtectedRouteProps) {
  const { user, isAuthenticated, isLoading } = useAuth();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  if (allowedTypes && user?.userType && !allowedTypes.includes(user.userType)) {
    return <Navigate to="/access-denied" replace />;
  }

  return <>{children}</>;
}

export default ProtectedRoute;

// Usage in App.tsx
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

Data Fetching ​

User List with Pagination ​

typescript
// pages/Users.tsx
import { useState } from 'react';
import { useBridgeApi, useBridgeQuery } from '@pubflow/react';

interface User {
  id: string;
  name: string;
  email: string;
  userType: string;
}

function Users() {
  const [page, setPage] = useState(1);
  const userService = useBridgeApi<User>({ endpoint: 'users' });
  
  const { data, meta, isLoading, error, refetch } = useBridgeQuery(
    userService,
    'list',
    { page, limit: 10, orderBy: 'name' }
  );

  if (isLoading) return <div>Loading users...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Users</h1>
      <button onClick={refetch}>Refresh</button>
      
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Type</th>
          </tr>
        </thead>
        <tbody>
          {data?.map(user => (
            <tr key={user.id}>
              <td>{user.name}</td>
              <td>{user.email}</td>
              <td>{user.userType}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <div className="pagination">
        <button 
          onClick={() => setPage(p => p - 1)} 
          disabled={page === 1}
        >
          Previous
        </button>
        <span>Page {page} of {Math.ceil((meta?.total || 0) / 10)}</span>
        <button 
          onClick={() => setPage(p => p + 1)} 
          disabled={!meta?.hasMore}
        >
          Next
        </button>
      </div>
    </div>
  );
}

export default Users;

Next Steps ​