Two levels of lifecycle — the App (the whole mini app) and the Page (one screen). Getting this right is why data loads once, screens stay fresh, and timers don't leak after the user leaves.
A Shoppingate mini app has two levels of lifecycle. The App lifecycle is about the whole mini app: starting, going to the background, coming back, and global errors. The Page lifecycle is about one screen: being opened, shown, hidden and closed, plus what the user does on that screen.
A simple way to think about it: the App is the shop building, and Pages are the rooms inside it. The building opens once in the morning (onLaunch), and it can close and reopen its doors (onHide / onShow). People walk in and out of the rooms all day (Page onLoad, onShow, onHide, onUnload).
app.js.js file, e.g. pages/product/product.jsApp({...})Page({...})globalData, shared by all pagesdata, only for that pagethis.setData({...})getApp()this (inside the page)onLaunch, onShow, onHide, onError, onPageNotFound, onUnhandledRejection, onThemeChangeonLoad, onShow, onReady, onHide, onUnload, onPullDownRefresh, onReachBottom, onShareAppMessage, onPageScroll, onTabItemTap, onResize, onAddToFavoritesonShow and onHideBoth App and Page have onShow and onHide, and they are easy to mix up.
onShow(options): path, query, scene, shareTicket, referrerInfoonHide only. Leaving the mini app (home screen, another app) triggers both Page onHide and App onHide.This is the order of calls in common situations:
Notice that App onLaunch and Page onLoad run only once each, while onShow and onHide can run many times.
The full sweep of an app.js from launch to shutdown, plus the four side-channels for errors, missing pages, uncaught rejections, and theme changes.
Mini App starts
│
App onLaunch(options) ← once: app initialized
│
App onShow(options) ← app visible (start, or back from background)
│
Page onLoad → Page onShow → Page onReady (first page opens)
│
│── script or API error → onError(error)
│── page does not exist → onPageNotFound(options)
│── Promise rejected, no catch → onUnhandledRejection(res)
│── system theme changes → onThemeChange(res)
│
Page onHide → App onHide() ← app goes to background
│
App onShow(options) ← app comes back to foregroundRules for the App examples:
App({...}) is called only once, in app.js. Pages are defined with Page({...}), the app with App({...}).globalData. A page reads it with getApp().globalData.onLaunch()When: Runs when the mini app has finished initializing. It runs only once.
options parameter tells you how the mini app was opened: path (the page opened), query (its parameters), scene (a number for the entry point, such as scanning a code or opening a shared card), shareTicket, and referrerInfo (the appId and extraData of the mini app or app that opened this one). A common use is getting the user's info with ft.getUserInfo() and saving it in globalData so every page can use it — see the Permissions page for the full flow.// app.js
App({
globalData: { userInfo: null },
onLaunch(options) {
console.log('Opened page:', options.path)
console.log('Query:', options.query)
console.log('Scene:', options.scene)
ft.getUserInfo({
success: (res) => {
this.globalData.userInfo = res.userInfo
},
fail: (err) => {
console.log('getUserInfo failed:', err)
}
})
}
})onShow()When: Runs when the mini app starts, or when it comes from the background to the foreground.
options as onLaunch, so you can see how the user came back — for example from a shared card or a scanned code.App({
onShow(options) {
console.log('App is visible, scene:', options.scene)
if (options.referrerInfo) {
console.log('Opened by appId:', options.referrerInfo.appId)
console.log('Extra data:', options.referrerInfo.extraData)
}
}
})onHide()When: Runs when the mini app moves from the foreground to the background.
App({
globalData: { cart: [] },
onHide() {
console.log('App moved to background')
ft.setStorageSync('cart', this.globalData.cart)
}
})onError()When: Runs when a script error happens, or when an API call fails.
error parameter is a string with the error message and stack. A common use is sending errors to your server so you know what went wrong on users' devices.App({
onError(error) {
console.log('App error:', error)
ft.request({
url: 'https://api.example.com/log/error',
method: 'POST',
data: { message: error },
success: () => {
console.log('Error reported')
},
fail: (err) => {
console.log('Request failed:', err)
}
})
}
})onPageNotFound()When: Runs when the mini app tries to open a page that does not exist.
options contains path (the missing page), query (its parameters) and isEntryPage (true if it is the first page of this launch — for example opened from an old shared link). Redirect the user to a page that exists. Use ft.switchTab instead if the target is a tab page.App({
onPageNotFound(options) {
console.log('Page not found:', options.path)
console.log('Is entry page:', options.isEntryPage)
ft.redirectTo({
url: '/pages/home/home'
})
}
})onUnhandledRejection()When: Runs when a Promise is rejected and nothing catches it.
res parameter contains reason (why it was rejected) and promise (the rejected Promise). It can also be set up with ft.onUnhandledRejection.App({
onUnhandledRejection(res) {
console.log('Unhandled rejection:', res.reason)
}
})
// Somewhere in a page: this rejection has no .catch(),
// so onUnhandledRejection runs
new Promise((resolve, reject) => {
reject('Something went wrong')
})onThemeChange()When: Runs when the system switches between light and dark theme.
res parameter contains theme, which is 'light' or 'dark'. It only works when "darkmode": true is set in app.json. It can also be set up with ft.onThemeChange.// app.json
{ "darkmode": true }
// app.js
App({
globalData: { theme: 'light' },
onThemeChange(res) {
console.log('New theme:', res.theme) // 'light' or 'dark'
this.globalData.theme = res.theme
}
})getApp(): using the app from a pagegetApp() returns the App instance, so any page can read globalData or call functions defined in App. Do not call it inside App itself — use this there.
// pages/profile/profile.js
Page({
data: { userInfo: null },
onLoad() {
const app = getApp()
this.setData({
userInfo: app.globalData.userInfo
})
}
})app.jsA real app usually uses only some of these. Here is a complete app.js that combines them.
// app.js
App({
globalData: {
userInfo: null,
cart: [],
theme: 'light'
},
onLaunch(options) {
console.log('App launched, scene:', options.scene)
this.globalData.cart = ft.getStorageSync('cart') || []
ft.getUserInfo({
success: (res) => {
this.globalData.userInfo = res.userInfo
},
fail: (err) => {
console.log('getUserInfo failed:', err)
}
})
},
onShow(options) {
console.log('App shown, path:', options.path)
},
onHide() {
ft.setStorageSync('cart', this.globalData.cart)
},
onError(error) {
ft.request({
url: 'https://api.example.com/log/error',
method: 'POST',
data: { message: error },
success: () => {
console.log('Error reported')
},
fail: (err) => {
console.log('Request failed:', err)
}
})
},
onPageNotFound(options) {
console.log('Page not found:', options.path)
ft.redirectTo({ url: '/pages/home/home' })
},
onUnhandledRejection(res) {
console.log('Unhandled rejection:', res.reason)
},
onThemeChange(res) {
this.globalData.theme = res.theme
}
})Learn onLaunch, onShow and onHide first. The rest are handlers for errors and special situations.
One page's life: created, shown, ready, hidden, destroyed — plus every event hook a page can react to.
Page opens
│
onLoad(query) ← once: read URL params, first API request
│
onShow() ← every time the page becomes visible
│
onReady() ← once: first render finished, UI is usable
│
│── user scrolls → onPageScroll()
│── user pulls down → onPullDownRefresh()
│── user reaches bottom → onReachBottom()
│── user shares → onShareAppMessage()
│── user favorites → onAddToFavorites()
│── user taps a tab → onTabItemTap()
│── window size changes → onResize()
│
onHide() ← page hidden (navigateTo, tab switch, app to background)
│ (can come back → onShow() again)
onUnload() ← page destroyed (redirectTo, navigateBack)Rules for the Page examples:
data, and you update the screen with this.setData({...}). Writing this.data.x = 1 or this.x = 1 does not update the view.loading) can be kept as plain fields on this instead of in data, which avoids unnecessary re-renders.onLoad()When: Runs when the page is loaded for the first time. It normally runs once.
query parameter contains parameters passed in the page URL. For example, a page opened with /pages/detail?id=123 can read query.id.// Opened with: ft.navigateTo({ url: '/pages/product/product?id=123' })
Page({
data: { product: null },
onLoad(query) {
const id = query.id // "123" (always a string!)
ft.request({
url: 'https://api.example.com/products/' + id,
success: (res) => {
this.setData({ product: res.data })
},
fail: (err) => {
console.log('Request failed:', err)
}
})
}
})onShow()When: Runs when the page becomes visible.
Page({
data: { cartCount: 0 },
onShow() {
ft.request({
url: 'https://api.example.com/cart/count',
success: (res) => {
this.setData({ cartCount: res.data.count })
},
fail: (err) => {
console.log('Request failed:', err)
}
})
}
})onReady()When: Runs when the page's initial rendering has finished.
Page({
onReady() {
ft.setNavigationBarTitle({ title: 'Product Details' })
}
})onHide()When: Runs when the page becomes hidden.
Page({
onHide() {
const video = ft.createVideoContext('promo')
video.pause()
}
})onUnload()When: Runs when the page is unloaded and removed.
Page({
onLoad() {
this.timer = setInterval(() => {
console.log('Running...')
}, 1000)
},
onUnload() {
clearInterval(this.timer)
}
})onPullDownRefresh()When: Runs when the user performs a pull-down refresh.
ft.stopPullDownRefresh() can stop the refresh animation.// product-list.json
{ "enablePullDownRefresh": true }
// product-list.js
Page({
data: { products: [] },
onPullDownRefresh() {
ft.request({
url: 'https://api.example.com/products',
success: (res) => {
this.setData({ products: res.data })
},
fail: (err) => {
console.log('Request failed:', err)
},
complete: () => {
ft.stopPullDownRefresh() // always stop the animation
}
})
}
})onReachBottom()When: Runs when the user scrolls near the bottom of the page.
Page({
data: { products: [], page: 1 },
onReachBottom() {
const nextPage = this.data.page + 1
ft.request({
url: 'https://api.example.com/products',
data: { page: nextPage },
success: (res) => {
this.setData({
products: this.data.products.concat(res.data),
page: nextPage
})
},
fail: (err) => {
console.log('Request failed:', err)
}
})
}
})onShareAppMessage()When: Runs when the user shares the mini app.
Page({
onShareAppMessage(options) {
console.log('Share from:', options.from) // 'button' or 'menu'
return {
title: 'Check out this product!',
path: '/pages/product/product?id=123'
}
}
})onPageScroll()When: Runs when the user scrolls the page.
Page({
data: { showBackToTop: false },
onPageScroll(options) {
const show = options.scrollTop > 300
if (show !== this.data.showBackToTop) {
this.setData({ showBackToTop: show })
}
}
})onTabItemTap()When: Runs when the user taps a bottom tab-bar item.
Page({
onTabItemTap(options) {
console.log('Tab index:', options.index)
console.log('Page path:', options.pagePath)
console.log('Tab text:', options.text)
}
})onResize()When: Runs when the window or screen size changes.
// product-list.json
{ "pageOrientation": "auto" }
// product-list.js
Page({
data: { layout: 'small' },
onResize(options) {
const width = options.size.windowWidth
this.setData({
layout: width >= 600 ? 'large' : 'small'
})
}
})onAddToFavorites()When: Runs when the user adds the page to Favorites.
Page({
onAddToFavorites(options) {
return {
title: 'My Product',
query: 'id=123'
}
}
})A real product page usually uses only some of these. Here is a complete page that combines the most common ones.
// pages/product/product.js
Page({
data: {
product: null,
reviews: [],
page: 1,
showBackToTop: false
},
onLoad(query) {
this.id = query.id
ft.request({
url: 'https://api.example.com/products/' + this.id,
success: (res) => {
this.setData({ product: res.data })
},
fail: (err) => {
console.log('Request failed:', err)
}
})
},
onShow() {
console.log('Page shown')
},
onReady() {
ft.setNavigationBarTitle({ title: 'Product Details' })
ft.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: '#07A35A'
})
ft.createSelectorQuery()
.select('#header')
.boundingClientRect((rect) => {
console.log('Header height:', rect.height)
})
.exec()
},
onHide() {
console.log('Page hidden')
},
onUnload() {
console.log('Page destroyed')
},
onPullDownRefresh() {
ft.request({
url: 'https://api.example.com/products/' + this.id,
success: (res) => {
this.setData({ product: res.data })
},
fail: (err) => {
console.log('Request failed:', err)
},
complete: () => {
ft.stopPullDownRefresh()
}
})
},
onReachBottom() {
const nextPage = this.data.page + 1
ft.request({
url: 'https://api.example.com/products/' + this.id + '/reviews',
data: { page: nextPage },
success: (res) => {
this.setData({
reviews: this.data.reviews.concat(res.data),
page: nextPage
})
},
fail: (err) => {
console.log('Request failed:', err)
}
})
},
onPageScroll(options) {
const show = options.scrollTop > 300
if (show !== this.data.showBackToTop) {
this.setData({ showBackToTop: show })
}
},
onShareAppMessage() {
return {
title: 'Check out this product!',
path: '/pages/product/product?id=' + this.id
}
},
onAddToFavorites() {
return { title: 'My Product', query: 'id=' + this.id }
}
})Learn onLoad, onShow, onReady, onHide and onUnload first. The rest are event handlers you add when you need a specific feature.