H5 List Page Caching Strategy and Implementation with CacheHoc
This article explains why list pages lose state in H5 applications, discusses the reasons behind it, compares Vue's keep-alive and React's lack of built‑in support, and presents a practical CacheHoc solution with code examples for storing list data and scroll positions using window storage.
Introduction
In everyday H5 development, navigating from a list to a detail page and back often causes the list state to be lost, which is especially common in e‑commerce platforms. Unlike native apps where the previous view is stacked, H5 pages are re‑mounted, triggering a new lifecycle and resetting pagination and scroll position, leading to poor user experience.
Thinking
Reason for State Loss
When routing changes, the component that does not match the new route is completely replaced, causing its state to disappear. Consequently, returning from a detail page reloads the list component and resets to the first page. Below is a typical route configuration example.
function RouterConfig({ history, app }) {
const routerData = getRouterData(app);
return (
<ConnectedRouter history={history}>
<Route path="/" render={(props) => <Layouts routerData={routerData} {...props} />} redirectPath="/exception/403" />
</ConnectedRouter>
);
}How to Solve
Two main approaches exist: automatic state saving during route changes or manual state saving. In Vue, the keep-alive component automatically caches deactivated components. React lacks a built‑in keep-alive and the official stance warns about memory leaks. Alternatives include using style‑based show/hide, state‑management tools like Redux or Mobx, or third‑party libraries such as react‑keep‑alive.
Key Considerations for Caching
What to Cache
Decide whether to cache the entire component, list data, or scroll container scrollTop. For example, WeChat articles cache the scroll position to resume reading where the user left off.
When to Store
Store data when navigating away with actions like PUSH, and retrieve it on POP actions. Different navigation actions (POP, PUSH, REPLACE) require different handling.
Where to Store
Persistent storage: URL parameters, localStorage, IndexedDB (recommended for larger data).
In‑memory storage: Redux, Rematch, or a global window object for quick read/write.
When to Retrieve
Retrieve cached data when the page initializes and the navigation action is POP. Other scenarios depend on specific business logic.
Where to Retrieve
Retrieve from the same location where the data was stored.
CacheHoc Solution
What to cache: list data + scroll container scroll height.
When to store: on page exit with a PUSH navigation.
Where to store: a global window object.
When to retrieve: during page initialization when the navigation action is POP.
Where to retrieve: from the window object. CacheHoc is a higher‑order component that saves data to window.CACHE_STORAGE using a unique CACHE_NAME key and restores it on mount.
Simple Usage
import React from 'react'
import { connect } from 'react-redux'
import cacheHoc from 'utils/cache_hoc'
@connect(mapStateToProps, mapDispatch)
@cacheHoc
export default class extends React.Component {
constructor(...props) {
super(...props)
this.props.withRef(this)
}
CACHE_NAME = `customerList${this.props.index}`
scrollDom = null
state = { orderBy: '2', loading: false, num: 1, dataSource: [], keyWord: undefined }
componentDidMount() {
// set scroll container reference and fetch data
}
render() {
const { history } = this.props
const { dataSource, orderBy, loading } = this.state
return (
<div className={gcmc('wrapper')}>
<MeScroll
className={gcmc('wrapper')}
getMs={ref => (this.scrollDom = ref)}
loadMore={this.fetchData}
refresh={this.refresh}
up={{ page: { num: 1, size: 15 } }}
down={{ auto: false }}
>
{loading ? (
<div className={gcmc('loading-wrapper')}><Loading /></div>
) : (
dataSource.map(item => (
<Card key={item.clienteleId} data={item} {...this.props} onClick={() => history.push('/detail/id')} />
))
)}
</MeScroll>
{/* sort UI omitted for brevity */}
</div>
)
}
}CacheHoc Implementation
const storeName = 'CACHE_STORAGE'
window[storeName] = {}
export default Comp => {
return class CacheWrapper extends Comp {
constructor(props) {
super(props)
if (!window[storeName][this.CACHE_NAME]) {
window[storeName][this.CACHE_NAME] = {}
}
const { history: { action } = {} } = props
if (action === 'POP') {
const { state = {} } = window[storeName][this.CACHE_NAME]
this.state = { ...state }
}
}
async componentDidMount() {
if (super.componentDidMount) await super.componentDidMount()
const { history: { action } = {} } = this.props
if (action !== 'POP') return
const { scrollTops = [] } = window[storeName][this.CACHE_NAME]
const { scrollElRefs = [] } = this
scrollElRefs.forEach((el, index) => {
if (el && el.scrollTop !== undefined) {
el.scrollTop = scrollTops[index]
}
})
}
componentWillUnmount() {
const { history: { action } = {} } = this.props
if (super.componentWillUnmount) super.componentWillUnmount()
if (action === 'PUSH') {
const scrollTops = []
const { scrollElRefs = [] } = this
scrollElRefs.forEach(ref => {
if (ref && ref.scrollTop !== undefined) {
scrollTops.push(ref.scrollTop)
}
})
window[storeName][this.CACHE_NAME] = { state: { ...this.state }, scrollTops }
}
if (action === 'POP') {
window[storeName][this.CACHE_NAME] = {}
}
}
}
}Conclusion
The presented CacheHoc is a simple implementation; more robust solutions could store data in IndexedDB or localStorage instead of the global window object to avoid data loss in multi‑page applications. Integration with mescroll requires checking the cached state in componentDidMount to prevent unnecessary re‑initialization.
Overall, caching strategies must consider what to cache, when to store, where to store, when to retrieve, and where to retrieve, adapting the approach to each page’s specific requirements.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
