Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9955fd4
docs(component): added missing components
joesphchang Jul 9, 2025
d4c5cda
docs(components): updated descriptions on components
joesphchang Jul 10, 2025
aeead46
docs(react): show complete code context in the "Your FirstApp" tutorial
joesphchang Jul 16, 2025
4ff6c31
docs(react): ran npm run lint and fixed spelling in docs
joesphchang Jul 17, 2025
2f52971
Merge branch 'main' into react-firstapp-content
joesphchang Jul 17, 2025
21cbe64
Merge branch 'main' into react-firstapp-content
thetaPC Oct 21, 2025
e361e12
docs(components): remove redundant
thetaPC Oct 21, 2025
1e5819f
Merge branch 'react-firstapp-content' of github.com:joesphchang/ionic…
thetaPC Oct 21, 2025
26ed242
Update docs/react/your-first-app.md
thetaPC Oct 21, 2025
32b0976
docs(react): update your first app page
thetaPC Oct 21, 2025
e3410a2
docs(react): update file paths
thetaPC Oct 21, 2025
2cefaad
docs(react): upate your first app page
thetaPC Oct 27, 2025
7e9bb4d
docs(react): update taking photos page
thetaPC Oct 27, 2025
290abfb
docs(react): update saving photos page
thetaPC Oct 27, 2025
a386a0c
docs(react): update loading photos page
thetaPC Oct 27, 2025
6072689
docs(react): update adding mobile page
thetaPC Oct 28, 2025
cb2cdff
docs(react): update live reload page
thetaPC Oct 28, 2025
978d7e6
docs(react): update your first app pages
thetaPC Oct 28, 2025
9788859
docs(react): update your first app pages
thetaPC Oct 28, 2025
524ad6a
docs(react): update your first app pages for v7
thetaPC Oct 29, 2025
360a5da
docs(react): use readFile variable
thetaPC Nov 10, 2025
52f3519
docs(react): use slice
thetaPC Nov 10, 2025
90a1aca
docs(react): remove periods from comments
thetaPC Nov 10, 2025
8b09582
docs(react): remove --capacitor
thetaPC Nov 10, 2025
71914da
docs(react): update Ionic styles comment
thetaPC Nov 10, 2025
fb65b85
docs(react): use better title change example
thetaPC Nov 10, 2025
ea823a9
docs(react): add file path
thetaPC Nov 10, 2025
80fb427
docs(react): remove redundant comment
thetaPC Nov 10, 2025
7cea235
docs(react): better click event listener example
thetaPC Nov 10, 2025
79fbfd4
docs(react): move existing code comment
thetaPC Nov 10, 2025
a13ce79
docs(react): update existing code comment
thetaPC Nov 10, 2025
ef8ae09
docs(react): add change comment
thetaPC Nov 10, 2025
8544a57
docs(react): update syntax
thetaPC Nov 10, 2025
b3489b4
docs(react): update imports
thetaPC Nov 10, 2025
6a96994
docs(react): remove first header
thetaPC Nov 10, 2025
a1152f7
docs(react): remove exclamation marks
thetaPC Nov 10, 2025
4d26d98
docs(react): add Camera
thetaPC Nov 10, 2025
8252e60
docs(react): update UserPhoto text
thetaPC Nov 10, 2025
5f77afe
docs(react): update Filesystem path
thetaPC Nov 10, 2025
2722fc6
docs(react): move note
thetaPC Nov 10, 2025
d9b9334
docs(react): add deployment text
thetaPC Nov 10, 2025
809f727
docs(react): use proper grammar
thetaPC Nov 10, 2025
7836499
chore(react): run lint
thetaPC Nov 10, 2025
4bd4108
docs(react): add default App
thetaPC Nov 10, 2025
e0cd901
docs(react): update title of page 8
thetaPC Nov 10, 2025
303fb12
docs(react): add default Tab2
thetaPC Nov 10, 2025
49de97c
docs(react): remove fab code
thetaPC Nov 10, 2025
b14dbab
docs(react): update page 7 example
thetaPC Nov 11, 2025
7f45e6f
docs(react): use important note
thetaPC Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Ionic apps are made of high-level building blocks called Components, which allow
<p>Action Sheets display a set of options with the ability to confirm or cancel an action.</p>
</DocsCard>

<DocsCard header="Action Sheet" href="api/action-sheet" icon="/icons/component-action-sheet-icon.png">
<p>Action Sheets display a set of options with the ability to confirm or cancel an action.</p>
</DocsCard>

<DocsCard header="Alert" href="api/alert" icon="/icons/component-alert-icon.png">
<p>Alerts are a great way to offer the user the ability to choose a specific action or list of actions.</p>
</DocsCard>
Expand Down
102 changes: 82 additions & 20 deletions docs/react/your-first-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,22 @@ After installation, open up the project in your code editor of choice.
Next, import `@ionic/pwa-elements` by editing `src/main.tsx`.

```tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
// CHANGE: Add the following import.
import { defineCustomElements } from '@ionic/pwa-elements/loader';

// Call the element loader before the render call
// CHANGE: Call the element loader before the render call
defineCustomElements(window);

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
```

That’s it! Now for the fun part - let’s see the app in action.
Expand Down Expand Up @@ -147,10 +159,12 @@ Open `/src/pages/Tab2.tsx`. We see:
<IonTitle>Photo Gallery</IonTitle>
```

We put the visual aspects of our app into `<IonContent>`. In this case, it’s where we’ll add a button that opens the device’s camera as well as displays the image captured by the camera. Start by adding a [floating action button](https://ionicframework.com/docs/api/fab) (FAB). First, update the imports at the top of the page to include the Camera icon as well as some of the Ionic components we'll use shortly:
We put the visual aspects of our app into `<ion-content>`. In this case, it’s where we’ll add a button that opens the device’s camera as well as displays the image captured by the camera. Start by adding a [floating action button](https://ionicframework.com/docs/api/fab) (FAB) to the bottom of the page and set the camera image as the icon.

```tsx
// CHANGE: Add the following import.
import { camera, trash, close } from 'ionicons/icons';
// CHANGE: Add the following import.
import {
IonContent,
IonHeader,
Expand All @@ -166,22 +180,33 @@ import {
IonImg,
IonActionSheet,
} from '@ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import './Tab2.css';

const Tab2: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Tab 2</IonTitle>
</IonToolbar>
</IonHeader>
<!-- CHANGE: Add the floating action button. -->
<IonContent>
<IonFab vertical="bottom" horizontal="center" slot="fixed">
<IonFabButton onClick={() => takePhoto()}>
<IonIcon icon={camera}></IonIcon>
</IonFabButton>
</IonFab>
</IonContent>
<!-- END OF CODE BLOCK -->
</IonPage>
);
};

export default Tab2;
```

Then, add the FAB to the bottom of the page. Use the camera image as the icon, and call the `takePhoto()` function when this button is clicked (to be implemented soon):

```tsx
<IonContent>
<IonFab vertical="bottom" horizontal="center" slot="fixed">
<IonFabButton onClick={() => takePhoto()}>
<IonIcon icon={camera}></IonIcon>
</IonFabButton>
</IonFab>
</IonContent>
```

We’ll be creating the `takePhoto` method and the logic to use the Camera and other native features in a moment.

Next, open `src/App.tsx`, remove the `ellipse` icon from the import and import the `images` icon instead:

```tsx
Expand All @@ -191,10 +216,47 @@ import { images, square, triangle } from 'ionicons/icons';
Within the tab bar (`<IonTabBar>`), change the label to “Photos” and the `ellipse` icon to `images` for the middle tab button:

```tsx
<IonTabButton tab="tab2" href="/tab2">
<IonIcon icon={images} />
<IonLabel>Photos</IonLabel>
</IonTabButton>
// Keep other imports
// CHANGE: Add the following import.
import { images, square, triangle } from 'ionicons/icons';

const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonTabs>
<IonRouterOutlet>
<Route exact path="/tab1">
<Tab1 />
</Route>
<Route exact path="/tab2">
<Tab2 />
</Route>
<Route path="/tab3">
<Tab3 />
</Route>
<Route exact path="/">
<Redirect to="/tab1" />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" href="/tab1">
<IonIcon aria-hidden="true" icon={triangle} />
<IonLabel>Tab 1</IonLabel>
</IonTabButton>
<!-- CHANGE: The label to `Photos` and the `ellipse` icon to `images` -->
<IonTabButton tab="tab2" href="/tab2">
<IonIcon icon={images} />
<IonLabel>Photos</IonLabel>
</IonTabButton>
<IonTabButton tab="tab3" href="/tab3">
<IonIcon aria-hidden="true" icon={square} />
<IonLabel>Tab 3</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonReactRouter>
</IonApp>
);
```

:::note
Expand Down
188 changes: 151 additions & 37 deletions docs/react/your-first-app/2-taking-photos.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,34 @@ Create a new file at `src/hooks/usePhotoGallery.ts` and open it up.
A custom hook is just a function that uses other React hooks. And that's what we will be doing! We will start by importing the various hooks and utilities we will be using from React core, the Ionic React Hooks project, and Capacitor:

```tsx
// CHANGE: Add the following imports
import { useState, useEffect } from 'react';
import { isPlatform } from '@ionic/react';

// CHANGE: Add the following imports
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
import { Capacitor } from '@capacitor/core';

export function usePhotoGallery() {}
```

Next, create a function named usePhotoGallery:

```tsx
import { useState, useEffect } from 'react';
import { isPlatform } from '@ionic/react';

import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
import { Capacitor } from '@capacitor/core';

export function usePhotoGallery() {
// CHANGE: Add the usePhotoGallery function.
const takePhoto = async () => {
// Take a photo
const photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
Expand All @@ -58,16 +72,34 @@ Notice the magic here: there's no platform-specific code (web, iOS, or Android)!
The last step we need to take is to use the new hook from the Tab2 page. Go back to Tab2.tsx and import the hook:

```tsx
import { usePhotoGallery } from '../hooks/usePhotoGallery';
```
// Keep the other imports

And right before the return statement in the functional component, get access to the `takePhoto` method by using the hook:
// CHANGE: Import the usePhotoGallery hook
import { usePhotoGallery } from '../hooks/usePhotoGallery';

```tsx
const Tab2: React.FC = () => {
// CHANGE: Get access to `takePhoto` method by using the hook
const { takePhoto } = usePhotoGallery();

// snip - rest of code
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Tab 2</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonFab vertical="bottom" horizontal="center" slot="fixed">
<IonFabButton onClick={() => takePhoto()}>
<IonIcon icon={camera}></IonIcon>
</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
);
};

export default Tab2;
```

Save the file, and if you’re not already, restart the development server in your browser by running `ionic serve`. On the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie!
Expand All @@ -83,6 +115,11 @@ After taking a photo, it disappears. We still need to display it within our app
First we will create a new type to define our Photo, which will hold specific metadata. Add the following UserPhoto interface to the `usePhotoGallery.ts` file, somewhere outside of the main function:

```tsx
export functino usePhotoGallery {
// Same old code from before.
}

// CHANGE: Add the interface.
export interface UserPhoto {
filepath: string;
webviewPath?: string;
Expand All @@ -92,53 +129,130 @@ export interface UserPhoto {
Back at the top of the function (right after the call to `usePhotoGallery`, we will define a state variable to store the array of each photo captured with the Camera.

```tsx
const [photos, setPhotos] = useState<UserPhoto[]>([]);
export function usePhotoGallery {
// CHANGE: Add the photos array.
const [photos, setPhotos] = useState<UserPhoto[]>([]);

// Same old code from before.
}
```

When the camera is done taking a picture, the resulting Photo returned from Capacitor will be stored in the `photo` variable. We want to create a new photo object and add it to the photos state array. We make sure we don't accidentally mutate the current photos array by making a new array, and then call `setPhotos` to store the array into state. Update the `takePhoto` method and add this code after the getPhoto call:

```tsx
const fileName = Date.now() + '.jpeg';
const newPhotos = [
{
filepath: fileName,
webviewPath: photo.webPath,
},
...photos,
];
setPhotos(newPhotos);
```
// Same old code from before.

Next, let's expose the photos array from our hook. Update the return statement to include the photos:
export function usePhotoGallery() {
const [photos, setPhotos] = useState<UserPhoto[]>([]);
// CHANGE: Create new fileName variable with date and .jpeg
const fileName = Date.now() + '.jpeg';

```tsx
return {
photos,
takePhoto,
};
const takePhoto = async () => {
// Same old code from before.

// CHANGE: Add in newPhotos after getPhoto call
const newPhotos = [
{
filepath: fileName,
webviewPath: photo.webPath,
},
...photos,
];
setPhotos(newPhotos);
};

// CHANGE: Update return statement to include photos.
return {
photos,
takePhoto,
};
}

// Same old code from before.
```

And back in the Tab2 component, get access to the photos:
`usePhotoGallery.ts` should now look like this:

```tsx
const { photos, takePhoto } = usePhotoGallery();
import { useState, useEffect } from 'react';
import { isPlatform } from '@ionic/react';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
import { Capacitor } from '@capacitor/core';

export function usePhotoGallery() {
const [photos, setPhotos] = useState<UserPhoto[]>([]);
const fileName = Date.now() + '.jpeg';

const takePhoto = async () => {
const photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});

const newPhotos = [
{
filepath: fileName,
webviewPath: photo.webPath,
},
...photos,
];
setPhotos(newPhotos);
};

return {
photos,
takePhoto,
};
}

export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
```

With the photo(s) stored into the main array we can display the images on the screen. Add a [Grid component](https://ionicframework.com/docs/api/grid) so that each photo will display nicely as photos are added to the gallery, and loop through each photo in the Photos array, adding an Image component (`<IonImg>`) for each. Point the `src` (source) to the photo’s path:
Next, move over to `Tab2.tsx` so we can display the image on the screen. With the photo(s) stored into the main array we can display the images on the screen. Add a [Grid component](https://ionicframework.com/docs/api/grid) so that each photo will display nicely as photos are added to the gallery, and loop through each photo in the Photos array, adding an Image component (`<IonImg>`) for each. Point the `src` (source) to the photo’s path:

```tsx
<IonContent>
<IonGrid>
<IonRow>
{photos.map((photo, index) => (
<IonCol size="6" key={photo.filepath}>
<IonImg src={photo.webviewPath} />
</IonCol>
))}
</IonRow>
</IonGrid>
<!-- <IonFab> markup -->
</IonContent>
// Same old code from before.

// CHANGE: Import usePhotoGallery Hook
import { usePhotoGallery } from '../hooks/usePhotoGallery';

const Tab2: React.FC = () => {
// CHANGE: Get access to photos from usePhotoGallery
const { photos, takePhoto } = usePhotoGallery();

return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Tab 2</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<!-- CHANGE: Add grid component and loop through each photo in the Photos Array. -->
<IonGrid>
<IonRow>
{photos.map((photo, index) => (
<IonCol size="6" key={photo.filepath}>
<IonImg src={photo.webviewPath} />
</IonCol>
))}
</IonRow>
</IonGrid>
<IonFab vertical="bottom" horizontal="center" slot="fixed">
<IonFabButton onClick={() => takePhoto()}>
<IonIcon icon={camera}></IonIcon>
</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
);
};
```

Save all files. Within the web browser, click the Camera button and take another photo. This time, the photo is displayed in the Photo Gallery!
Expand Down
Loading
Loading