--- id: with-ionic-react title: 'Quickstart: Ionic React' description: Learn how to use Supabase in your Ionic React App. sidebar_label: Ionic React --- import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' import QuickstartIntro from './_partials/quickstart_intro.md' import ProjectSetup from './_partials/project_setup.mdx' ![Supabase User Management example](/img/ionic-demos/ionic-angular-account.png) ### GitHub Should you get stuck while working through the guide, refer to [this repo](https://github.com/mhartington/supabase-ionic-react). ## Building the App Let's start building the React app from scratch. ### Initialize an Ionic React app We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize an app called `supabase-ionic-react`: ```bash npm install -g @ionic/cli ionic start supabase-ionic-react blank --type react cd supabase-ionic-react ``` Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js) ```bash npm install @supabase/supabase-js ``` And finally we want to save the environment variables in a `.env`. All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). ```bash title=".env" REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```js title="src/supabaseClient.js" import { createClient } from '@supabase/supabase-js' const supabaseUrl = process.env.REACT_APP_SUPABASE_URL const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey) ``` ### Set up a Login route Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. ```jsx title="/src/pages/Login.tsx" import { useState } from 'react'; import { IonButton, IonContent, IonHeader, IonInput, IonItem, IonLabel, IonList, IonPage, IonTitle, IonToolbar, useIonToast, useIonLoading, } from '@ionic/react'; import { supabase } from '../supabaseClient'; export function LoginPage() { const [email, setEmail] = useState(''); const [showLoading, hideLoading] = useIonLoading(); const [showToast ] = useIonToast(); const handleLogin = async (e: React.FormEvent) => { console.log() e.preventDefault(); await showLoading(); try { await supabase.auth.signIn({ email }); await showToast({ message: 'Check your email for the login link!' }); } catch (e: any) { await showToast({ message: e.error_description || e.message , duration: 5000}); } finally { await hideLoading(); } }; return ( Login

Supabase + Ionic React

Sign in via magic link with your email below

Email setEmail(e.detail.value ?? '')} type="email" >
Login
); } ``` ### Account page After a user is signed in we can allow them to edit their profile details and manage their account. Let's create a new component for that called `Account.tsx`. ```jsx title="src/pages/Account.tsx" import { IonButton, IonContent, IonHeader, IonInput, IonItem, IonLabel, IonPage, IonTitle, IonToolbar, useIonLoading, useIonToast, useIonRouter } from '@ionic/react'; import { useEffect, useState } from 'react'; import { supabase } from '../supabaseClient'; export function AccountPage() { const [showLoading, hideLoading] = useIonLoading(); const [showToast] = useIonToast(); const [session] = useState(() => supabase.auth.session()); const router = useIonRouter(); const [profile, setProfile] = useState({ username: '', website: '', avatar_url: '', }); useEffect(() => { getProfile(); }, [session]); const getProfile = async () => { console.log('get'); await showLoading(); try { const user = supabase.auth.user(); let { data, error, status } = await supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', user!.id) .single(); if (error && status !== 406) { throw error; } if (data) { setProfile({ username: data.username, website: data.website, avatar_url: data.avatar_url, }); } } catch (error: any) { showToast({ message: error.message, duration: 5000 }); } finally { await hideLoading(); } }; const signOut = async () => { await supabase.auth.signOut(); router.push('/', 'forward', 'replace'); } const updateProfile = async (e?: any, avatar_url: string = '') => { e?.preventDefault(); console.log('update '); await showLoading(); try { const user = supabase.auth.user(); const updates = { id: user!.id, ...profile, avatar_url: avatar_url, updated_at: new Date(), }; let { error } = await supabase.from('profiles').upsert(updates, { returning: 'minimal', // Don't return the value after inserting }); if (error) { throw error; } } catch (error: any) { showToast({ message: error.message, duration: 5000 }); } finally { await hideLoading(); } }; return ( Account

Email

{session?.user?.email}

Name setProfile({ ...profile, username: e.detail.value ?? '' }) } > Website setProfile({ ...profile, website: e.detail.value ?? '' }) } >
Update Profile
Log Out
); } ``` ### Launch! Now that we have all the components in place, let's update `App.tsx`: ```jsx title="src/App.tsx" import { Redirect, Route } from 'react-router-dom' import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react' import { IonReactRouter } from '@ionic/react-router' import { supabase } from './supabaseClient' import '@ionic/react/css/ionic.bundle.css' /* Theme variables */ import './theme/variables.css' import { LoginPage } from './pages/Login' import { AccountPage } from './pages/Account' import { useEffect, useState } from 'react' import { Session } from '@supabase/supabase-js' setupIonicReact() const App: React.FC = () => { const [session, setSession] = (useState < Session) | (null > null) useEffect(() => { setSession(supabase.auth.session()) supabase.auth.onAuthStateChange((_event, session) => { setSession(session) }) }, [session]) return ( { return session ? : }} /> ) } export default App ``` Once that's done, run this in a terminal window: ```bash ionic serve ``` And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app. ![Supabase Ionic React](/img/ionic-demos/ionic-react.png) ## Bonus: Profile photos Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos. ### Create an upload widget First install two packages in order to interact with the user's camera. ```bash npm install @ionic/pwa-elements @capacitor/camera ``` [CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native deavice API. Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI. With those packages installed we can update our `index.tsx` to include an additional bootstapping call for the Ionic PWA Elements. ```ts title="src/index.tsx" import React from 'react' import ReactDOM from 'react-dom' import App from './App' import * as serviceWorkerRegistration from './serviceWorkerRegistration' import reportWebVitals from './reportWebVitals' import { defineCustomElements } from '@ionic/pwa-elements/loader' defineCustomElements(window) ReactDOM.render( , document.getElementById('root') ) serviceWorkerRegistration.unregister() reportWebVitals() ``` Then create an **AvatarComponent**. ```jsx title="src/components/Avatar.tsx" import { IonIcon } from '@ionic/react'; import { person } from 'ionicons/icons'; import { Camera, CameraResultType } from '@capacitor/camera'; import { useEffect, useState } from 'react'; import { supabase } from '../supabaseClient'; import './Avatar.css' export function Avatar({ url, onUpload, }: { url: string; onUpload: (e: any, file: string) => Promise; }) { const [avatarUrl, setAvatarUrl] = useState(); useEffect(() => { if (url) { downloadImage(url); } }, [url]); const uploadAvatar = async () => { try { const photo = await Camera.getPhoto({ resultType: CameraResultType.DataUrl, }); const file = await fetch(photo.dataUrl!) .then((res) => res.blob()) .then( (blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }) ); const fileName = `${Math.random()}-${new Date().getTime()}.${ photo.format }`; let { error: uploadError } = await supabase.storage .from('avatars') .upload(fileName, file); if (uploadError) { throw uploadError; } onUpload(null, fileName); } catch (error) { console.log(error); } }; const downloadImage = async (path: string) => { try { const { data, error } = await supabase.storage .from('avatars') .download(path); if (error) { throw error; } const url = URL.createObjectURL(data!); setAvatarUrl(url); } catch (error: any) { console.log('Error downloading image: ', error.message); } }; return (
{avatarUrl ? ( ) : ( )}
); } ``` ### Add the new widget And then we can add the widget to the Account page: ```jsx title="src/pages/Account.tsx" // Import the new component import { Avatar } from '../components/Avatar'; // ... return ( Account ``` ## Next steps At this stage you have a fully functional application! - Got a question? [Ask here](https://github.com/supabase/supabase/discussions). - Sign in: [app.supabase.com](https://app.supabase.com)