Skip to content

@pubflow/react-native ​

React Native integration for Flowfull Clients with AsyncStorage, offline support, and native components optimized for mobile.

Expo Support

This package is optimized for Expo and works seamlessly with:

  • ✅ Expo (Recommended) - Full support with SecureStore and AsyncStorage
  • ✅ Expo Router - File-based routing with Expo
  • ✅ React Native CLI - Works with bare React Native projects

Secure Storage: Automatically uses Expo SecureStore when available, with AsyncStorage fallback.

Installation ​

bash
npm install @pubflow/core @pubflow/react-native swr
npm install @react-native-async-storage/async-storage

For Expo projects:

bash
npx expo install @pubflow/core @pubflow/react-native swr @react-native-async-storage/async-storage expo-secure-store

Quick Start ​

typescript
import { PubflowProvider, useAuth, useBridgeQuery } from '@pubflow/react-native';
import { SafeAreaView, Text, Button, FlatList } from 'react-native';

export default function App() {
  return (
    <PubflowProvider url={process.env.EXPO_PUBLIC_BACKEND_URL!}>
      <SafeAreaView style={{ flex: 1 }}>
        <Dashboard />
      </SafeAreaView>
    </PubflowProvider>
  );
}

function Dashboard() {
  const { user, logout } = useAuth();
  const { data, error, isLoading } = useBridgeQuery('/api/users');

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

  return (
    <>
      <Text>Welcome, {user?.name}!</Text>
      <Button title="Logout" onPress={logout} />
      <FlatList
        data={data}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => <Text>{item.name}</Text>}
      />
    </>
  );
}

Provider ​

PubflowProvider ​

Same API as React, but with mobile-optimized storage.

typescript
import { PubflowProvider } from '@pubflow/react-native';

<PubflowProvider
  url={process.env.EXPO_PUBLIC_BACKEND_URL!}
  instanceId="default"
  onAuthError={(error) => console.error('Auth error:', error)}
  onSessionExpired={() => {
    // Navigate to login screen
    navigation.navigate('Login');
  }}
>
  <App />
</PubflowProvider>

Hooks ​

All hooks from @pubflow/react are available with the same API:

  • useAuth() - Authentication state and methods
  • useBridgeQuery() - Data fetching with SWR
  • useBridgeMutation() - Data mutations
  • useBridgeCrud() - Complete CRUD operations
  • useBridgeApi() - Direct API access

See @pubflow/react documentation for detailed hook documentation.

Components ​

BridgeView ​

Mobile-optimized data display component.

typescript
import { BridgeView } from '@pubflow/react-native';
import { FlatList, Text } from 'react-native';

<BridgeView
  endpoint="/api/users"
  render={(data) => (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  )}
  loading={<Text>Loading users...</Text>}
  error={(error) => <Text>Error: {error.message}</Text>}
/>

BridgeList ​

FlatList with automatic data fetching, pull-to-refresh, and infinite scroll.

typescript
import { BridgeList } from '@pubflow/react-native';
import { View, Text } from 'react-native';

<BridgeList
  endpoint="/api/users"
  renderItem={(user) => (
    <View style={{ padding: 10 }}>
      <Text style={{ fontSize: 18 }}>{user.name}</Text>
      <Text style={{ color: '#666' }}>{user.email}</Text>
    </View>
  )}
  emptyMessage="No users found"
  pullToRefresh
  infiniteScroll
/>

Props ​

PropTypeRequiredDescription
endpointstringYesAPI endpoint to fetch from
renderItem(item: T) => ReactNodeYesRender function for each item
emptyMessagestringNoMessage when list is empty
pullToRefreshbooleanNoEnable pull-to-refresh
infiniteScrollbooleanNoEnable infinite scroll
pageSizenumberNoItems per page (default: 20)
ListHeaderComponentReactNodeNoHeader component
ListFooterComponentReactNodeNoFooter component

BridgeForm ​

Form component with native inputs and validation.

typescript
import { BridgeForm } from '@pubflow/react-native';
import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

<BridgeForm
  endpoint="/api/users"
  method="POST"
  schema={userSchema}
  fields={[
    { name: 'name', label: 'Name', type: 'text' },
    { name: 'email', label: 'Email', type: 'email', keyboardType: 'email-address' },
  ]}
  onSuccess={(data) => {
    console.log('User created:', data);
    navigation.goBack();
  }}
  submitLabel="Create User"
/>

OfflineIndicator ​

Display connection status with native styling.

typescript
import { OfflineIndicator } from '@pubflow/react-native';

<OfflineIndicator
  position="top"
  message="You are offline"
  style={{ backgroundColor: '#ff0000' }}
  textStyle={{ color: '#fff' }}
/>

AdvancedFilter ​

Advanced filtering component for mobile.

typescript
import { AdvancedFilter } from '@pubflow/react-native';

<AdvancedFilter
  fields={[
    { name: 'name', label: 'Name', type: 'text' },
    { name: 'status', label: 'Status', type: 'select', options: ['active', 'inactive'] },
  ]}
  onFilter={(filters) => console.log('Filters:', filters)}
/>

Storage ​

SecureStorageAdapter ​

Automatically uses Expo SecureStore for sensitive data with AsyncStorage fallback.

typescript
import { SecureStorageAdapter } from '@pubflow/react-native';

const storage = new SecureStorageAdapter();

// Session IDs are stored in SecureStore (encrypted)
await storage.setItem('pubflow_session_id', 'ses_...');

// User data is stored in AsyncStorage (not encrypted)
await storage.setItem('pubflow_user_data', JSON.stringify(user));

Storage Keys ​

  • pubflow_session_id - Session ID (SecureStore)
  • pubflow_user_data - User data (AsyncStorage)
  • pubflow_instance_id - Instance ID (AsyncStorage)

Offline Support ​

React Native package includes built-in offline support:

Offline Queue ​

Mutations are queued when offline and executed when connection is restored.

typescript
import { useBridgeMutation } from '@pubflow/react-native';

function CreatePost() {
  const { trigger, isMutating } = useBridgeMutation('/api/posts', 'POST');

  const handleCreate = async () => {
    // This will be queued if offline
    await trigger({ title: 'New Post', content: 'Content...' });
  };

  return <Button title="Create Post" onPress={handleCreate} disabled={isMutating} />;
}

Network Detection ​

Automatically detects network status and shows offline indicator.

typescript
import { useNetworkStatus } from '@pubflow/react-native';

function MyComponent() {
  const { isOnline, isOffline } = useNetworkStatus();

  return (
    <View>
      {isOffline && <Text>You are offline</Text>}
      {isOnline && <Text>You are online</Text>}
    </View>
  );
}

Examples ​

Login Screen ​

typescript
import { useAuth } from '@pubflow/react-native';
import { useState } from 'react';
import { View, TextInput, Button, Text, StyleSheet } from 'react-native';

export function LoginScreen({ navigation }) {
  const { login, isLoading, error } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async () => {
    try {
      await login({ email: email.toLowerCase(), password });
      navigation.replace('Home');
    } catch (err) {
      console.error('Login failed:', err);
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        value={email}
        onChangeText={(text) => setEmail(text.toLowerCase())}
        placeholder="Email"
        keyboardType="email-address"
        autoCapitalize="none"
        autoCorrect={false}
      />
      <TextInput
        style={styles.input}
        value={password}
        onChangeText={setPassword}
        placeholder="Password"
        secureTextEntry
      />
      {error && <Text style={styles.error}>{error.message}</Text>}
      <Button
        title={isLoading ? 'Logging in...' : 'Login'}
        onPress={handleLogin}
        disabled={isLoading}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 20 },
  input: {
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    marginBottom: 10,
    borderRadius: 5,
  },
  error: { color: 'red', marginBottom: 10 },
});

User List with Pull-to-Refresh ​

typescript
import { BridgeList } from '@pubflow/react-native';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

export function UserListScreen({ navigation }) {
  return (
    <BridgeList
      endpoint="/api/users"
      renderItem={(user) => (
        <TouchableOpacity
          style={styles.item}
          onPress={() => navigation.navigate('UserDetail', { userId: user.id })}
        >
          <Text style={styles.name}>{user.name}</Text>
          <Text style={styles.email}>{user.email}</Text>
        </TouchableOpacity>
      )}
      emptyMessage="No users found"
      pullToRefresh
      infiniteScroll
    />
  );
}

const styles = StyleSheet.create({
  item: {
    padding: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  name: { fontSize: 18, fontWeight: 'bold' },
  email: { fontSize: 14, color: '#666', marginTop: 5 },
});

Create User Form ​

typescript
import { BridgeForm } from '@pubflow/react-native';
import { View } from 'react-native';
import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  phone: z.string().optional(),
});

export function CreateUserScreen({ navigation }) {
  return (
    <View style={{ padding: 20 }}>
      <BridgeForm
        endpoint="/api/users"
        method="POST"
        schema={userSchema}
        fields={[
          { name: 'name', label: 'Name', type: 'text' },
          { name: 'email', label: 'Email', type: 'email', keyboardType: 'email-address' },
          { name: 'phone', label: 'Phone', type: 'text', keyboardType: 'phone-pad' },
        ]}
        onSuccess={(data) => {
          console.log('User created:', data);
          navigation.goBack();
        }}
        submitLabel="Create User"
      />
    </View>
  );
}

Environment Variables ​

For Expo projects, use EXPO_PUBLIC_ prefix:

bash
# .env
EXPO_PUBLIC_BACKEND_URL=https://your-backend.com
typescript
// Usage
<PubflowProvider url={process.env.EXPO_PUBLIC_BACKEND_URL!}>

TypeScript Support ​

Full TypeScript support with type inference:

typescript
import { useBridgeQuery } from '@pubflow/react-native';

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

function UserList() {
  const { data } = useBridgeQuery<User[]>('/api/users');
  // data is typed as User[] | undefined
}

Next Steps ​