Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add infinite scroll example using useSWRInfinite #970

Merged
merged 1 commit into from
Feb 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions examples/infinite-scroll/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# useSWRInfinite with scroll based on IntersectionObserver

## One-Click Deploy

Deploy your own SWR project with Vercel.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/project?template=https://github.com/vercel/swr/tree/master/examples/infinite-scroll)

## How to Use

Download the example:

```bash
curl https://codeload.github.com/vercel/swr/tar.gz/master | tar -xz --strip=2 swr-master/examples/infinite-scroll
cd basic
```

Install it and run:

```bash
yarn
yarn dev
# or
npm install
npm run dev
```

Deploy it to the cloud with [now](https://vercel.com/home) ([download](https://vercel.com/download))

```
now
```

## The Idea behind the Example

Show usage of useSWRInfinite with infinite scroll based on IntersectionObserver
21 changes: 21 additions & 0 deletions examples/infinite-scroll/hooks/useOnScreen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState, useEffect } from 'react'

const useOnScreen = (ref) => {
const [isIntersecting, setIntersecting] = useState(false)

useEffect(() => {
const observer = new IntersectionObserver(([entry]) =>
setIntersecting(entry.isIntersecting)
)

observer.observe(ref.current)
// Remove the observer as soon as the component is unmounted
return () => {
observer.disconnect()
}
}, [])

return isIntersecting
}

export default useOnScreen
6 changes: 6 additions & 0 deletions examples/infinite-scroll/libs/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import fetch from 'isomorphic-unfetch'

export default async function (...args) {
const res = await fetch(...args)
return res.json()
}
18 changes: 18 additions & 0 deletions examples/infinite-scroll/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "infinite-scroll",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"isomorphic-unfetch": "3.0.0",
"next": "10.0.6",
"react": "17.0.1",
"react-dom": "17.0.1",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
82 changes: 82 additions & 0 deletions examples/infinite-scroll/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useSWRInfinite } from 'swr'
import { useState, useRef, useEffect } from 'react'

import fetcher from '../libs/fetch'
import useOnScreen from '../hooks/useOnScreen'

const PAGE_SIZE = 6

const getKey = (pageIndex, previousPageData, repo, pageSize) => {
if (previousPageData && !previousPageData.length) return null // reached the end

return `https://github.com/gitapi/repos/${repo}/issues?per_page=${pageSize}&page=${
pageIndex + 1
}`
}

export default function App() {
const ref = useRef()
const [repo, setRepo] = useState('facebook/react')
const [val, setVal] = useState(repo)

const isVisible = useOnScreen(ref)

const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(
(...args) => getKey(...args, repo, PAGE_SIZE),
fetcher
)

const issues = data ? [].concat(...data) : []
const isLoadingInitialData = !data && !error
const isLoadingMore =
isLoadingInitialData ||
(size > 0 && data && typeof data[size - 1] === 'undefined')
const isEmpty = data?.[0]?.length === 0
const isReachingEnd = size === PAGE_SIZE
const isRefreshing = isValidating && data && data.length === size

useEffect(() => {
if (isVisible && !isReachingEnd && !isRefreshing) {
setSize(size + 1)
}
}, [isVisible, isRefreshing])

return (
<div style={{ fontFamily: 'sans-serif' }}>
<input
value={val}
onChange={(e) => setVal(e.target.value)}
placeholder="facebook/reect"
/>
<button
onClick={() => {
setRepo(val)
setSize(1)
}}
>
load issues
</button>
<p>
showing {size} page(s) of {isLoadingMore ? '...' : issues.length}{' '}
issue(s){' '}
<button disabled={isRefreshing} onClick={() => mutate()}>
{isRefreshing ? 'refreshing...' : 'refresh'}
</button>
<button disabled={!size} onClick={() => setSize(0)}>
clear
</button>
</p>
{isEmpty ? <p>Yay, no issues found.</p> : null}
{issues.map((issue) => {
return (
<p key={issue.id} style={{ margin: '6px 0', height: 50 }}>
- {issue.title}
</p>
)
})}
<div ref={ref}>
{isLoadingMore ? 'loading...' : isReachingEnd ? 'no more issues' : ''}
</div>
</div>
)
}