implement usePromise hook
This commit is contained in:
parent
7fdbef7573
commit
322d249740
@ -1,16 +1,12 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
|
||||
import type { Account } from '../../../shared/types.ts'
|
||||
|
||||
const AccountsPage: FunctionComponent = () => {
|
||||
const [accounts, setAccounts] = useState<Account[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
rek('/api/accounts').then(setAccounts)
|
||||
}, [])
|
||||
const accounts = usePromise<Account[]>(() => rek('/api/accounts'))
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useCallback, useEffect } from 'preact/hooks'
|
||||
import { useCallback, useEffect, useRef } from 'preact/hooks'
|
||||
import { LocationProvider, Route, Router } from 'preact-iso'
|
||||
import { get } from 'lowline'
|
||||
import Head from './head.ts'
|
||||
@ -16,30 +16,47 @@ type Props = {
|
||||
title?: string
|
||||
}
|
||||
|
||||
const remember = throttle(function remember() {
|
||||
window.history.replaceState(
|
||||
{
|
||||
...window.history.state,
|
||||
scrollTop: window.scrollY,
|
||||
},
|
||||
'',
|
||||
null,
|
||||
)
|
||||
}, 100)
|
||||
const scroll = () => {
|
||||
const offset = get(window.history, 'state.scrollTop')
|
||||
|
||||
window.scrollTo(0, offset || 0)
|
||||
}
|
||||
|
||||
const App: FunctionComponent<Props> = ({ error, title }) => {
|
||||
const loadRef = useRef<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
history.scrollRestoration = 'manual'
|
||||
|
||||
const remember = throttle(function remember() {
|
||||
if (loadRef.current) return
|
||||
|
||||
window.history.replaceState(
|
||||
{
|
||||
...window.history.state,
|
||||
scrollTop: window.scrollY,
|
||||
},
|
||||
'',
|
||||
null,
|
||||
)
|
||||
}, 100)
|
||||
|
||||
addEventListener('scroll', remember)
|
||||
|
||||
return () => removeEventListener('scroll', remember)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const onLoadStart = useCallback(() => {
|
||||
loadRef.current = true
|
||||
}, [])
|
||||
|
||||
const onLoadEnd = useCallback(() => {
|
||||
scroll()
|
||||
loadRef.current = false
|
||||
}, [])
|
||||
|
||||
const onRouteChange = useCallback(() => {
|
||||
const offset = get(window.history, 'state.scrollTop')
|
||||
|
||||
setTimeout(() => {
|
||||
window.scrollTo(0, offset || 0)
|
||||
}, 100)
|
||||
if (!loadRef.current) scroll()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@ -55,7 +72,7 @@ const App: FunctionComponent<Props> = ({ error, title }) => {
|
||||
{error ? (
|
||||
<ErrorPage error={error} />
|
||||
) : (
|
||||
<Router onRouteChange={onRouteChange}>
|
||||
<Router onLoadStart={onLoadStart} onLoadEnd={onLoadEnd} onRouteChange={onRouteChange}>
|
||||
{routes.map((route) => (
|
||||
<Route key={route.path} path={route.path} component={route.component} />
|
||||
))}
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import cn from 'classnames'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import { formatNumber } from '../utils/format_number.ts'
|
||||
import s from './balances_page.module.scss'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
|
||||
import type { Balance, FinancialYear } from '../../../shared/types.ts'
|
||||
|
||||
const BalancesPage: FunctionComponent = () => {
|
||||
const [balances, setBalances] = useState<Balance[]>([])
|
||||
const [years, setYears] = useState<number[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/balances`).then(setBalances)
|
||||
rek(`/api/financial-years`).then((financialYears: FinancialYear[]) => setYears(financialYears.map((fy) => fy.year)))
|
||||
}, [])
|
||||
const [balances, years] = usePromise<[Balance[], number[]]>(() =>
|
||||
Promise.all([
|
||||
rek(`/api/balances`),
|
||||
rek(`/api/financial-years`).then((financialYears: FinancialYear[]) => financialYears.map((fy) => fy.year)),
|
||||
]),
|
||||
)
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
||||
@ -1,22 +1,14 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useRoute } from 'preact-iso'
|
||||
import { formatNumber } from '../utils/format_number.ts'
|
||||
import rek from 'rek'
|
||||
|
||||
import { type Entry } from '../../../shared/types.ts'
|
||||
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
import { formatNumber } from '../utils/format_number.ts'
|
||||
import Head from './head.ts'
|
||||
|
||||
const EntryPage: FunctionComponent = () => {
|
||||
const [entry, setEntry] = useState<Entry | null>(null)
|
||||
const route = useRoute()
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/entries/${route.params.id}`).then(setEntry)
|
||||
}, [])
|
||||
|
||||
if (!entry) return
|
||||
const entry = usePromise<Entry>(() => rek(`/api/entries/${route.params.id}`))
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useRoute } from 'preact-iso'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import { formatNumber } from '../utils/format_number.ts'
|
||||
|
||||
import type { Invoice } from '../../../shared/types.ts'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
import { formatNumber } from '../utils/format_number.ts'
|
||||
import Head from './head.ts'
|
||||
|
||||
const InvoicePage: FunctionComponent = () => {
|
||||
const [invoice, setInvoice] = useState<Invoice | null>(null)
|
||||
const invoice = usePromise<Invoice>(() => rek(`/api/invoices/${route.params.id}`))
|
||||
const route = useRoute()
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/invoices/${route.params.id}`).then(setInvoice)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Head>
|
||||
|
||||
@ -1,26 +1,24 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
// @ts-ignore
|
||||
import Format from 'easy-tz/format'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useRoute } from 'preact-iso'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
import type { Invoice, Supplier } from '../../../shared/types.ts'
|
||||
|
||||
const format = Format.bind(null, null, 'YYYY.MM.DD')
|
||||
|
||||
const InvoicesPage: FunctionComponent = () => {
|
||||
const [supplier, setSupplier] = useState<Supplier | null>(null)
|
||||
const [invoices, setInvoices] = useState<Invoice[]>([])
|
||||
const [totalAmount, setTotalAmount] = useState<number | null>(null)
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/suppliers/${route.params.supplier}`).then(setSupplier)
|
||||
rek(`/api/invoices?supplier=${route.params.supplier}`).then(setInvoices)
|
||||
rek(`/api/invoices/total-amount?supplier=${route.params.supplier}`).then(setTotalAmount)
|
||||
}, [route.params.supplier])
|
||||
const [supplier, invoices, totalAmount] = usePromise<[Supplier, Invoice[], number]>(() =>
|
||||
Promise.all([
|
||||
rek(`/api/suppliers/${route.params.supplier}`),
|
||||
rek(`/api/invoices?supplier=${route.params.supplier}`),
|
||||
rek(`/api/invoices/total-amount?supplier=${route.params.supplier}`),
|
||||
]),
|
||||
)
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
|
||||
import type { Supplier } from '../../../shared/types.ts'
|
||||
|
||||
const InvoicesPage: FunctionComponent = () => {
|
||||
const [suppliers, setSuppliers] = useState<Supplier[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
rek('/api/suppliers').then(setSuppliers)
|
||||
}, [])
|
||||
const suppliers = usePromise<Supplier[]>(() => rek('/api/suppliers'))
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
import type { Object as ObjectType } from '../../../shared/types.ts'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
import Head from './head.ts'
|
||||
import Object from './object.tsx'
|
||||
import s from './results_page.module.scss'
|
||||
import type { Object as ObjectType } from '../../../shared/types.ts'
|
||||
|
||||
const ObjectsPage: FunctionComponent = () => {
|
||||
const [objects, setObjects] = useState<ObjectType[]>([])
|
||||
const objects = usePromise<ObjectType[]>(() => rek('/api/objects'))
|
||||
const [currentObject, setCurrentObject] = useState<ObjectType | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
rek('/api/objects').then((objects: ObjectType[]) => setObjects(objects))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Head>
|
||||
|
||||
@ -1,21 +1,19 @@
|
||||
import { h, type FunctionComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import cn from 'classnames'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import type { FinancialYear, Result } from '../../../shared/types.ts'
|
||||
import { formatNumber } from '../utils/format_number.ts'
|
||||
import usePromise from '../../shared/hooks/use_promise.ts'
|
||||
import Head from './head.ts'
|
||||
import s from './results_page.module.scss'
|
||||
|
||||
import type { FinancialYear, Result } from '../../../shared/types.ts'
|
||||
|
||||
const ResultsPage: FunctionComponent = () => {
|
||||
const [results, setResults] = useState<Result[]>([])
|
||||
const [years, setYears] = useState<number[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/results`).then((results: Result[]) => setResults(results))
|
||||
rek(`/api/financial-years`).then((financialYears: FinancialYear[]) => setYears(financialYears.map((fy) => fy.year)))
|
||||
}, [])
|
||||
const [results, years] = usePromise<[Result[], number[]]>(() =>
|
||||
Promise.all([
|
||||
rek(`/api/results`),
|
||||
rek(`/api/financial-years`).then((financialYears: FinancialYear[]) => financialYears.map((fy) => fy.year)),
|
||||
]),
|
||||
)
|
||||
|
||||
return (
|
||||
<section>
|
||||
|
||||
17
client/shared/hooks/use_promise.ts
Normal file
17
client/shared/hooks/use_promise.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useRef, useState } from 'preact/hooks'
|
||||
|
||||
export default function usePromise<R = any, E = Error>(fn: () => Promise<R>) {
|
||||
const [result, setResult] = useState<R | null>(null)
|
||||
const [error, setError] = useState<E | null>(null)
|
||||
const promiseRef = useRef<Promise<void> | null>(null)
|
||||
|
||||
if (!promiseRef.current) {
|
||||
throw (promiseRef.current = fn().then(setResult, setError))
|
||||
} else if (result) {
|
||||
return result
|
||||
} else if (error) {
|
||||
throw error
|
||||
} else {
|
||||
throw promiseRef.current
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@
|
||||
"pg": "^8.16.3",
|
||||
"pg-protocol": "^1.10.3",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"preact": "^10.27.2",
|
||||
"preact": "^10.28.0",
|
||||
"preact-iso": "^2.11.0",
|
||||
"rek": "^0.8.1"
|
||||
},
|
||||
|
||||
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@ -60,11 +60,11 @@ importers:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
preact:
|
||||
specifier: ^10.27.2
|
||||
version: 10.27.2
|
||||
specifier: ^10.28.0
|
||||
version: 10.28.0
|
||||
preact-iso:
|
||||
specifier: ^2.11.0
|
||||
version: 2.11.0(preact-render-to-string@6.6.3(preact@10.27.2))(preact@10.27.2)
|
||||
version: 2.11.0(preact-render-to-string@6.6.3(preact@10.28.0))(preact@10.28.0)
|
||||
rek:
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1
|
||||
@ -74,10 +74,10 @@ importers:
|
||||
version: 7.28.5
|
||||
'@preact/preset-vite':
|
||||
specifier: ^2.10.1
|
||||
version: 2.10.2(@babel/core@7.28.5)(preact@10.27.2)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))
|
||||
version: 2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))
|
||||
'@testing-library/preact':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(preact@10.27.2)
|
||||
version: 3.2.4(preact@10.28.0)
|
||||
'@types/d3-dsv':
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7
|
||||
@ -1895,8 +1895,8 @@ packages:
|
||||
peerDependencies:
|
||||
preact: '>=10 || >= 11.0.0-0'
|
||||
|
||||
preact@10.27.2:
|
||||
resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==}
|
||||
preact@10.28.0:
|
||||
resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==}
|
||||
|
||||
prettier@3.6.2:
|
||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||
@ -2830,12 +2830,12 @@ snapshots:
|
||||
|
||||
'@pinojs/redact@0.4.0': {}
|
||||
|
||||
'@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.27.2)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))':
|
||||
'@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5)
|
||||
'@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5)
|
||||
'@prefresh/vite': 2.4.11(preact@10.27.2)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))
|
||||
'@prefresh/vite': 2.4.11(preact@10.28.0)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.28.5)
|
||||
debug: 4.4.3
|
||||
@ -2848,20 +2848,20 @@ snapshots:
|
||||
|
||||
'@prefresh/babel-plugin@0.5.2': {}
|
||||
|
||||
'@prefresh/core@1.5.9(preact@10.27.2)':
|
||||
'@prefresh/core@1.5.9(preact@10.28.0)':
|
||||
dependencies:
|
||||
preact: 10.27.2
|
||||
preact: 10.28.0
|
||||
|
||||
'@prefresh/utils@1.2.1': {}
|
||||
|
||||
'@prefresh/vite@2.4.11(preact@10.27.2)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))':
|
||||
'@prefresh/vite@2.4.11(preact@10.28.0)(vite@7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@prefresh/babel-plugin': 0.5.2
|
||||
'@prefresh/core': 1.5.9(preact@10.27.2)
|
||||
'@prefresh/core': 1.5.9(preact@10.28.0)
|
||||
'@prefresh/utils': 1.2.1
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
preact: 10.27.2
|
||||
preact: 10.28.0
|
||||
vite: 7.2.4(@types/node@24.10.1)(sass@1.94.2)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@ -2948,10 +2948,10 @@ snapshots:
|
||||
lz-string: 1.5.0
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/preact@3.2.4(preact@10.27.2)':
|
||||
'@testing-library/preact@3.2.4(preact@10.28.0)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 8.20.1
|
||||
preact: 10.27.2
|
||||
preact: 10.28.0
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
@ -3942,16 +3942,16 @@ snapshots:
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
|
||||
preact-iso@2.11.0(preact-render-to-string@6.6.3(preact@10.27.2))(preact@10.27.2):
|
||||
preact-iso@2.11.0(preact-render-to-string@6.6.3(preact@10.28.0))(preact@10.28.0):
|
||||
dependencies:
|
||||
preact: 10.27.2
|
||||
preact-render-to-string: 6.6.3(preact@10.27.2)
|
||||
preact: 10.28.0
|
||||
preact-render-to-string: 6.6.3(preact@10.28.0)
|
||||
|
||||
preact-render-to-string@6.6.3(preact@10.27.2):
|
||||
preact-render-to-string@6.6.3(preact@10.28.0):
|
||||
dependencies:
|
||||
preact: 10.27.2
|
||||
preact: 10.28.0
|
||||
|
||||
preact@10.27.2: {}
|
||||
preact@10.28.0: {}
|
||||
|
||||
prettier@3.6.2: {}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user