Skip to content

Commit

Permalink
feat: feature complete
Browse files Browse the repository at this point in the history
  • Loading branch information
simoneb committed Dec 31, 2020
1 parent c1f9974 commit 94d32d1
Show file tree
Hide file tree
Showing 28 changed files with 922 additions and 114 deletions.
5 changes: 5 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"test": { "presets": ["@babel/preset-env", "@babel/preset-react"] }
}
}
4 changes: 3 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cjs/
es/
umd.js
umd/
coverage/
examples/umd.js
9 changes: 8 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
"plugin:react-hooks/recommended"
],
"env": {
"browser": true,
"node": true,
"es6": true
"es6": true,
"jest": true
},
"settings": {
"react": {
"version": "detect"
}
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules/
cjs/
es/
umd/
coverage/

.eslintcache
package-lock.json
Expand Down
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
examples/
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# token-pagination-hooks

React Hooks library to use classic pagination in the frontend with a token-based paginatiom backend

## Setup

`npm i token-pagination-hooks`

## Quickstart

The API you're using

- accepts a `pageToken` query string parameter to do pagination
- returns data in the format:

```json
{
"data": [...],
"nextPage": "some opaque string"
}
```

Assuming you're using a library like [axios-hooks](https://github.com/simoneb/axios-hooks/) to interact with the API:

```js
import React, { useState } from 'react'
import useAxios from 'axios-hooks'
import useTokenPagination from 'token-pagination-hooks'

function Pagination() {
const [pageNumber, setPageNumber] = useState(1)
const { currentToken, useUpdateToken } = useTokenPagination(pageNumber)
const [{ data }] = useAxios({ url: '/api', params: { pageToken: currentToken })
}
```
17 changes: 17 additions & 0 deletions examples/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"env": {
"browser": true,
"node": true
},
"parserOptions": { "sourceType": "script" },
"globals": {
"React": true,
"PropTypes": true,
"useTokenPagination": true,
"Output": true,
"Source": true,
"SimpleDeclarative": true,
"SimpleImperative": true,
"InternalState": true
}
}
30 changes: 30 additions & 0 deletions examples/components/Example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const choices = [SimpleDeclarative, SimpleImperative, InternalState].reduce(
(acc, c) => ({ ...acc, [c.name]: c }),
{}
)

function Example() {
const [choice, setChoice] = React.useState(SimpleDeclarative.name)

const Component = choices[choice]

return (
<div>
<select value={choice} onChange={e => setChoice(e.target.value)}>
{Object.keys(choices).map(c => (
<option key={c}>{c}</option>
))}
</select>
<div style={{ display: 'flex' }}>
<div>
<Component />
</div>
<div style={{ marginLeft: '3rem' }}>
<Source fileName={choice} />
</div>
</div>
</div>
)
}

Example.propTypes = {}
56 changes: 56 additions & 0 deletions examples/components/InternalState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const { useState, useEffect } = React

function InternalState() {
const {
currentToken,
useUpdateToken,
changePageNumber,
changePageSize,
pageNumber,
pageSize,
} = useTokenPagination({ defaultPageNumber: 1, defaultPageSize: 5 })

const [data, setData] = useState()

useEffect(() => {
async function fetchData() {
const params = new URLSearchParams({ pageSize })

if (currentToken) {
params.append('pageToken', currentToken)
}

const res = await fetch(`/api?${params.toString()}`)
const data = await res.json()

setData(data)
}

fetchData()
}, [pageSize, currentToken])

useUpdateToken(data?.nextPage)

function previousPage() {
changePageNumber(n => n - 1)
}
function nextPage() {
changePageNumber(n => n + 1)
}
function handleChangePageSize(e) {
changePageSize(+e.target.value)
}

return (
<Output
data={data}
pageNumber={pageNumber}
pageSize={pageSize}
changePageSize={handleChangePageSize}
previousPage={previousPage}
nextPage={nextPage}
/>
)
}

InternalState.propTypes = {}
45 changes: 45 additions & 0 deletions examples/components/Output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const T = PropTypes

function Output({
data,
pageNumber,
pageSize,
changePageSize,
previousPage,
nextPage,
}) {
return (
<>
<pre>{JSON.stringify(data, null, 2)}</pre>
<div>
<select value={pageSize} onChange={changePageSize}>
<option value={3}>3</option>
<option value={5}>5</option>
<option value={12}>12</option>
</select>
{' '}

<button disabled={pageNumber <= 1} onClick={previousPage}>
&lt;&lt;
</button>
{' '}
{pageNumber}
{' '}
<button disabled={!data?.nextPage} onClick={nextPage}>
&gt;&gt;
</button>
</div>
</>
)
}

Output.propTypes = {
data: T.shape({
nextPage: T.any,
}),
pageNumber: T.number.isRequired,
pageSize: T.number.isRequired,
changePageSize: T.func.isRequired,
previousPage: T.func.isRequired,
nextPage: T.func.isRequired,
}
53 changes: 53 additions & 0 deletions examples/components/SimpleDeclarative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { useState, useEffect } = React

function SimpleDeclarative() {
const [{ pageNumber, pageSize }, setPagination] = useState({
pageNumber: 1,
pageSize: 3,
})
const { currentToken, useUpdateToken } = useTokenPagination(pageNumber)

const [data, setData] = useState()

useEffect(() => {
async function fetchData() {
const params = new URLSearchParams({ pageSize })

if (currentToken) {
params.append('pageToken', currentToken)
}

const res = await fetch(`/api?${params.toString()}`)
const data = await res.json()

setData(data)
}

fetchData()
}, [pageSize, currentToken])

useUpdateToken(data?.nextPage)

function previousPage() {
setPagination(s => ({ ...s, pageNumber: pageNumber - 1 }))
}
function nextPage() {
setPagination(s => ({ ...s, pageNumber: pageNumber + 1 }))
}
function changePageSize(e) {
setPagination({ pageSize: e.target.value, pageNumber: 1 })
}

return (
<Output
data={data}
pageNumber={pageNumber}
pageSize={pageSize}
changePageSize={changePageSize}
previousPage={previousPage}
nextPage={nextPage}
/>
)
}

SimpleDeclarative.propTypes = {}
53 changes: 53 additions & 0 deletions examples/components/SimpleImperative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { useState, useEffect } = React

function SimpleImperative() {
const [{ pageNumber, pageSize }, setPagination] = useState({
pageNumber: 1,
pageSize: 3,
})
const { currentToken, updateToken } = useTokenPagination(pageNumber)

const [data, setData] = useState()

useEffect(() => {
async function fetchData() {
const params = new URLSearchParams({ pageSize })

if (currentToken) {
params.append('pageToken', currentToken)
}

const res = await fetch(`/api?${params.toString()}`)
const data = await res.json()

updateToken(data.nextPage)

setData(data)
}

fetchData()
}, [pageSize, currentToken, updateToken])

function previousPage() {
setPagination(s => ({ ...s, pageNumber: pageNumber - 1 }))
}
function nextPage() {
setPagination(s => ({ ...s, pageNumber: pageNumber + 1 }))
}
function changePageSize(e) {
setPagination({ pageSize: e.target.value, pageNumber: 1 })
}

return (
<Output
data={data}
pageNumber={pageNumber}
pageSize={pageSize}
changePageSize={changePageSize}
previousPage={previousPage}
nextPage={nextPage}
/>
)
}

SimpleImperative.propTypes = {}
29 changes: 29 additions & 0 deletions examples/components/Source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { useState, useEffect } = React
const T = PropTypes

function Source({ fileName }) {
const [source, setSource] = useState()

useEffect(() => {
async function fetchSource() {
const res = await fetch(`/components/${fileName}.js`)

setSource(await res.text())
}

fetchSource()
}, [fileName])

return (
<div>
<h3>{fileName} source code</h3>
<pre>
<code>{source}</code>
</pre>
</div>
)
}

Source.propTypes = {
fileName: T.string.isRequired,
}
24 changes: 24 additions & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Example</title>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/prop-types@15/prop-types.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="umd.js"></script>
<script type="text/babel" src="components/Output.js"></script>
<script type="text/babel" src="components/Source.js"></script>
<script type="text/babel" src="components/SimpleDeclarative.js"></script>
<script type="text/babel" src="components/SimpleImperative.js"></script>
<script type="text/babel" src="components/InternalState.js"></script>
<script type="text/babel" src="components/Example.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(<Example />, document.getElementById('root'))
</script>
</body>
</html>
Loading

0 comments on commit 94d32d1

Please sign in to comment.