mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat: refetch latest data
This commit is contained in:
parent
26c1c7cf92
commit
7bd0b762b5
18
src/atoms.ts
18
src/atoms.ts
@ -1,14 +1,28 @@
|
||||
import { atom } from "jotai"
|
||||
import type { SectionID, SourceID } from "@shared/types"
|
||||
import { metadata, sourceList } from "@shared/data"
|
||||
import { atomWithLocalStorage } from "./utils/atom"
|
||||
import { atomWithLocalStorage } from "./hooks/atomWithLocalStorage"
|
||||
|
||||
export const focusSourcesAtom = atomWithLocalStorage<SourceID[]>("focusSources", [], (stored) => {
|
||||
return stored.filter(item => item in sourceList)
|
||||
})
|
||||
|
||||
const currentSectionIDAtom = atom<SectionID>("focus")
|
||||
function initRefetchSource() {
|
||||
let time = 0
|
||||
// useOnReload
|
||||
// 没有放在 useOnReload 里面, 可以避免初始化后再修改 refetchSourceAtom,导致多次请求 API
|
||||
const _ = localStorage.getItem("quitTime")
|
||||
const now = Date.now()
|
||||
const quitTime = _ ? Number(_) : 0
|
||||
if (!Number.isNaN(quitTime) && now - quitTime < 1000) {
|
||||
time = now
|
||||
}
|
||||
return Object.fromEntries(Object.keys(sourceList).map(k => [k, time])) as Record<SourceID, number>
|
||||
}
|
||||
|
||||
export const refetchSourceAtom = atom(initRefetchSource())
|
||||
|
||||
const currentSectionIDAtom = atom<SectionID>("focus")
|
||||
export const currentSectionAtom = atom((get) => {
|
||||
const id = get(currentSectionIDAtom)
|
||||
if (id === "focus") {
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import { useCallback } from "react"
|
||||
import { useAtomValue } from "jotai"
|
||||
import type { SourceID } from "@shared/types"
|
||||
import { queryClient } from "~/main"
|
||||
import { useAtomValue, useSetAtom } from "jotai"
|
||||
import logo from "~/assets/react.svg"
|
||||
import { useDark } from "~/hooks/useDark"
|
||||
import { currentSectionAtom } from "~/atoms"
|
||||
import { currentSectionAtom, refetchSourceAtom } from "~/atoms"
|
||||
|
||||
function ThemeToggle() {
|
||||
const { toggleDark } = useDark()
|
||||
@ -21,13 +19,14 @@ function ThemeToggle() {
|
||||
|
||||
function RefreshButton() {
|
||||
const currentSection = useAtomValue(currentSectionAtom)
|
||||
const refreshAll = useCallback(async () => {
|
||||
await queryClient.refetchQueries({
|
||||
predicate(query) {
|
||||
return currentSection.sourceList.includes(query.queryKey[0] as SourceID)
|
||||
},
|
||||
})
|
||||
}, [currentSection])
|
||||
const setRefetchSource = useSetAtom(refetchSourceAtom)
|
||||
const refreshAll = useCallback(() => {
|
||||
const obj = Object.fromEntries(currentSection.sourceList.map(id => [id, Date.now()]))
|
||||
setRefetchSource(prev => ({
|
||||
...prev,
|
||||
...obj,
|
||||
}))
|
||||
}, [currentSection, setRefetchSource])
|
||||
|
||||
return (
|
||||
<button type="button" className="i-ph:arrow-clockwise btn-pure" onClick={refreshAll} />
|
||||
|
@ -5,7 +5,7 @@ import clsx from "clsx"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
import { useAtom, useAtomValue } from "jotai"
|
||||
import { useCallback } from "react"
|
||||
import { currentSectionAtom, focusSourcesAtom } from "~/atoms"
|
||||
import { currentSectionAtom, focusSourcesAtom, refetchSourceAtom } from "~/atoms"
|
||||
|
||||
export function Main() {
|
||||
const currentSection = useAtomValue(currentSectionAtom)
|
||||
@ -40,15 +40,32 @@ function NewsCard({ id, inView }: { id: SourceID, inView: boolean }) {
|
||||
const addFocusList = useCallback(() => {
|
||||
setFocusSources(focusSources.includes(id) ? focusSources.filter(i => i !== id) : [...focusSources, id])
|
||||
}, [setFocusSources, focusSources, id])
|
||||
const { isPending, error, isFetching, data, refetch } = useQuery({
|
||||
queryKey: [id],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`/api/${id}?latest`)
|
||||
const [refetchSource, setRefetchSource] = useAtom(refetchSourceAtom)
|
||||
const { isPending, error, isFetching, data } = useQuery({
|
||||
queryKey: [id, refetchSource[id]],
|
||||
queryFn: async ({ queryKey }) => {
|
||||
const [_id, _refetchTime] = queryKey as [SourceID, number]
|
||||
let url = `/api/${_id}`
|
||||
if (Date.now() - _refetchTime < 1000) {
|
||||
url = `/api/${_id}?latest`
|
||||
}
|
||||
const response = await fetch(url)
|
||||
return await response.json() as SourceInfo
|
||||
},
|
||||
// refetch 时显示原有的数据
|
||||
placeholderData: prev => prev,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
enabled: inView,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnWindowFocus: true,
|
||||
})
|
||||
|
||||
const manualRefetch = useCallback(() => {
|
||||
setRefetchSource(prev => ({
|
||||
...prev,
|
||||
[id]: Date.now(),
|
||||
}))
|
||||
}, [setRefetchSource, id])
|
||||
|
||||
if (isPending || !data) {
|
||||
return (
|
||||
<>
|
||||
@ -84,8 +101,14 @@ function NewsCard({ id, inView }: { id: SourceID, inView: boolean }) {
|
||||
<span>
|
||||
{formatTime(data!.updateTime)}
|
||||
</span>
|
||||
<button type="button" className={clsx(focusSources.includes(id) ? "i-ph:star-fill" : "i-ph:star")} onClick={addFocusList} />
|
||||
<button type="button" className={clsx("i-ph:arrow-clockwise", isFetching && "animate-spin")} onClick={() => refetch()} />
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
className={clsx("i-ph:arrow-clockwise", isFetching && "animate-spin")}
|
||||
onClick={manualRefetch}
|
||||
/>
|
||||
<button type="button" className={clsx(focusSources.includes(id) ? "i-ph:star-fill" : "i-ph:star")} onClick={addFocusList} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
20
src/hooks/useOnReload.ts
Normal file
20
src/hooks/useOnReload.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useEffect } from "react"
|
||||
import { useBeforeUnload } from "react-use"
|
||||
|
||||
export function useOnReload(fn?: () => Promise<void> | void, fallback?: () => Promise<void> | void) {
|
||||
useBeforeUnload(() => {
|
||||
localStorage.setItem("quitTime", Date.now().toString())
|
||||
return false
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const _ = localStorage.getItem("quitTime")
|
||||
const quitTime = _ ? Number(_) : 0
|
||||
if (!Number.isNaN(quitTime) && Date.now() - quitTime < 1000) {
|
||||
fn?.()
|
||||
} else {
|
||||
fallback?.()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}
|
@ -6,6 +6,7 @@ import "@unocss/reset/tailwind.css"
|
||||
import "virtual:uno.css"
|
||||
import type { QueryClient } from "@tanstack/react-query"
|
||||
import { Header } from "~/components/Header"
|
||||
import { useOnReload } from "~/hooks/useOnReload"
|
||||
|
||||
export const Route = createRootRouteWithContext<{
|
||||
queryClient: QueryClient
|
||||
@ -21,6 +22,7 @@ export const Route = createRootRouteWithContext<{
|
||||
})
|
||||
|
||||
export function RootComponent() {
|
||||
useOnReload()
|
||||
return (
|
||||
<div className="p-10">
|
||||
<Header />
|
||||
|
Loading…
x
Reference in New Issue
Block a user