The starter's first tab becomes BookNest's catalog, the six books in an IonList. Tapping one gives a haptic tap, stores its id with Preferences (Preferences), marks it "Last" and shows a toast. The plugin calls are ordinary awaited imports inside a handler and an Ionic 20,753 lifecycle hook:
// Ionic lifecycle hook: runs every time the tab becomes visible, not just on mount
useIonViewWillEnter(() => {
Preferences.get({ key: 'lastBookId' }).then(({ value }) => setLastId(Number(value)));
});
const pick = async (book: Book) => {
await Haptics.impact({ style: ImpactStyle.Light }); // navigator.vibrate() on the web
await Preferences.set({ key: 'lastBookId', value: String(book.id) });
setLastId(book.id);
console.log(`catalog: lastBookId=${book.id}`);
presentToast({ message: `Saved "${book.title}"`, duration: 1500, position: 'bottom' });
};lastId is React 7,897 state and presentToast comes from useIonToast(); the JSX maps books to <IonItem button onClick={() => pick(book)}> rows with an <IonBadge> on the last pick. Tapped on the emulator, the app logged the call, and Preferences wrote shared_prefs/CapacitorStorage.xml just as in the Framework7 572,986 app:
adb -s emulator-5558 logcat -d -v tag | grep "catalog:"I/Capacitor/Console: File: https://localhost/assets/index-DRDzrfDQ.js - Line 10 - Msg: catalog: lastBookId=3
Keep plugin calls in handlers and hooks, never in the render body, which React may run many times, and know each plugin's browser path: Haptics calls navigator.vibrate() and rejects where it is missing, as in Safari 10 , which would stop pick() before it saves. Wrap such optional calls in try.