Skip to content

React Native Examples ​

Complete examples for using Flowfull Clients with React Native (Expo).

Basic Setup ​

App Setup with Provider ​

typescript
// App.tsx
import { PubflowProvider } from '@pubflow/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import LoginScreen from './screens/LoginScreen';
import HomeScreen from './screens/HomeScreen';
import UsersScreen from './screens/UsersScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <PubflowProvider
      config={{
        baseUrl: process.env.EXPO_PUBLIC_BACKEND_URL!,
        bridgeBasePath: '/bridge',
        authBasePath: '/auth',
        useSecureStorage: true
      }}
    >
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Login" component={LoginScreen} />
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Users" component={UsersScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    </PubflowProvider>
  );
}

Authentication ​

Login Screen ​

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

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

  async function handleLogin() {
    setError('');
    
    try {
      const result = await login({ email, password });
      
      if (result.success) {
        navigation.replace('Home');
      } else {
        setError(result.error || 'Login failed');
      }
    } catch (err) {
      setError('An error occurred');
    }
  }

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    marginBottom: 10,
    borderRadius: 5,
  },
  error: {
    color: 'red',
    marginTop: 10,
    textAlign: 'center',
  },
});

Data Fetching ​

User List with Pull-to-Refresh ​

typescript
// screens/UsersScreen.tsx
import { useState } from 'react';
import { View, FlatList, Text, TouchableOpacity, RefreshControl, StyleSheet } from 'react-native';
import { useBridgeApi, useBridgeQuery } from '@pubflow/react-native';

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

export default function UsersScreen() {
  const [page, setPage] = useState(1);
  const userService = useBridgeApi<User>({ endpoint: 'users' });
  
  const { data, isLoading, error, refetch } = useBridgeQuery(
    userService,
    'list',
    { page, limit: 20 }
  );

  if (error) {
    return (
      <View style={styles.center}>
        <Text>Error: {error.message}</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <FlatList
        data={data || []}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <TouchableOpacity style={styles.item}>
            <Text style={styles.name}>{item.name}</Text>
            <Text style={styles.email}>{item.email}</Text>
          </TouchableOpacity>
        )}
        refreshControl={
          <RefreshControl refreshing={isLoading} onRefresh={refetch} />
        }
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  item: {
    padding: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  name: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  email: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
});

Protected Screen ​

typescript
// screens/HomeScreen.tsx
import { View, Text, Button, StyleSheet } from 'react-native';
import { useAuth } from '@pubflow/react-native';

export default function HomeScreen({ navigation }: any) {
  const { user, logout } = useAuth();

  async function handleLogout() {
    await logout();
    navigation.replace('Login');
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome, {user?.name}!</Text>
      <Text style={styles.subtitle}>{user?.email}</Text>
      
      <Button title="View Users" onPress={() => navigation.navigate('Users')} />
      <Button title="Logout" onPress={handleLogout} color="red" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 10,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginBottom: 30,
    textAlign: 'center',
  },
});

Next Steps ​