Skip to content

Data Fetching ​

This guide covers data fetching patterns using Flowfull Client.

Basic Data Fetching ​

Simple GET Request ​

typescript
import { useState, useEffect } from 'react';
import { api } from '../lib/api';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const data = await api.get('/users');
        setUsers(data);
      } catch (err: any) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    
    fetchUsers();
  }, []);

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

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Pagination ​

typescript
function PaginatedUserList() {
  const [users, setUsers] = useState([]);
  const [page, setPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    fetchUsers();
  }, [page]);

  async function fetchUsers() {
    setLoading(true);
    try {
      const response = await api.query('/users')
        .page(page)
        .limit(10)
        .execute();
      
      setUsers(response.data);
      setTotalPages(Math.ceil(response.meta.total / 10));
    } catch (error) {
      console.error('Error fetching users:', error);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      {loading && <div>Loading...</div>}
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      <div>
        <button
          onClick={() => setPage(p => Math.max(1, p - 1))}
          disabled={page === 1 || loading}
        >
          Previous
        </button>
        <span>Page {page} of {totalPages}</span>
        <button
          onClick={() => setPage(p => Math.min(totalPages, p + 1))}
          disabled={page === totalPages || loading}
        >
          Next
        </button>
      </div>
    </div>
  );
}

Filtering and Sorting ​

typescript
function FilteredUserList() {
  const [users, setUsers] = useState([]);
  const [status, setStatus] = useState('all');
  const [sortBy, setSortBy] = useState('name');
  const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');

  useEffect(() => {
    fetchUsers();
  }, [status, sortBy, sortDir]);

  async function fetchUsers() {
    try {
      let query = api.query('/users');
      
      if (status !== 'all') {
        query = query.filter('status', status);
      }
      
      query = query.orderBy(sortBy, sortDir);
      
      const data = await query.execute();
      setUsers(data);
    } catch (error) {
      console.error('Error fetching users:', error);
    }
  }

  return (
    <div>
      <select value={status} onChange={(e) => setStatus(e.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="inactive">Inactive</option>
      </select>
      
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
        <option value="name">Name</option>
        <option value="email">Email</option>
        <option value="created_at">Created At</option>
      </select>
      
      <button onClick={() => setSortDir(d => d === 'asc' ? 'desc' : 'asc')}>
        {sortDir === 'asc' ? '↑' : '↓'}
      </button>

      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Search with Debouncing ​

typescript
import { useState, useEffect } from 'react';
import { api } from '../lib/api';

function SearchableUserList() {
  const [query, setQuery] = useState('');
  const [debouncedQuery, setDebouncedQuery] = useState('');
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  // Debounce search query
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedQuery(query);
    }, 300);

    return () => clearTimeout(timer);
  }, [query]);

  // Fetch users when debounced query changes
  useEffect(() => {
    if (debouncedQuery) {
      searchUsers();
    } else {
      setUsers([]);
    }
  }, [debouncedQuery]);

  async function searchUsers() {
    setLoading(true);
    try {
      const data = await api.query('/users')
        .filter('name', 'like', debouncedQuery)
        .limit(10)
        .execute();
      
      setUsers(data);
    } catch (error) {
      console.error('Error searching users:', error);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search users..."
      />
      {loading && <div>Searching...</div>}
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Next Steps ​