mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-31 10:58:04 +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 { atom } from "jotai"
|
||||||
import type { SectionID, SourceID } from "@shared/types"
|
import type { SectionID, SourceID } from "@shared/types"
|
||||||
import { metadata, sourceList } from "@shared/data"
|
import { metadata, sourceList } from "@shared/data"
|
||||||
import { atomWithLocalStorage } from "./utils/atom"
|
import { atomWithLocalStorage } from "./hooks/atomWithLocalStorage"
|
||||||
|
|
||||||
export const focusSourcesAtom = atomWithLocalStorage<SourceID[]>("focusSources", [], (stored) => {
|
export const focusSourcesAtom = atomWithLocalStorage<SourceID[]>("focusSources", [], (stored) => {
|
||||||
return stored.filter(item => item in sourceList)
|
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) => {
|
export const currentSectionAtom = atom((get) => {
|
||||||
const id = get(currentSectionIDAtom)
|
const id = get(currentSectionIDAtom)
|
||||||
if (id === "focus") {
|
if (id === "focus") {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import { useCallback } from "react"
|
import { useCallback } from "react"
|
||||||
import { useAtomValue } from "jotai"
|
import { useAtomValue, useSetAtom } from "jotai"
|
||||||
import type { SourceID } from "@shared/types"
|
|
||||||
import { queryClient } from "~/main"
|
|
||||||
import logo from "~/assets/react.svg"
|
import logo from "~/assets/react.svg"
|
||||||
import { useDark } from "~/hooks/useDark"
|
import { useDark } from "~/hooks/useDark"
|
||||||
import { currentSectionAtom } from "~/atoms"
|
import { currentSectionAtom, refetchSourceAtom } from "~/atoms"
|
||||||
|
|
||||||
function ThemeToggle() {
|
function ThemeToggle() {
|
||||||
const { toggleDark } = useDark()
|
const { toggleDark } = useDark()
|
||||||
@ -21,13 +19,14 @@ function ThemeToggle() {
|
|||||||
|
|
||||||
function RefreshButton() {
|
function RefreshButton() {
|
||||||
const currentSection = useAtomValue(currentSectionAtom)
|
const currentSection = useAtomValue(currentSectionAtom)
|
||||||
const refreshAll = useCallback(async () => {
|
const setRefetchSource = useSetAtom(refetchSourceAtom)
|
||||||
await queryClient.refetchQueries({
|
const refreshAll = useCallback(() => {
|
||||||
predicate(query) {
|
const obj = Object.fromEntries(currentSection.sourceList.map(id => [id, Date.now()]))
|
||||||
return currentSection.sourceList.includes(query.queryKey[0] as SourceID)
|
setRefetchSource(prev => ({
|
||||||
},
|
...prev,
|
||||||
})
|
...obj,
|
||||||
}, [currentSection])
|
}))
|
||||||
|
}, [currentSection, setRefetchSource])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button" className="i-ph:arrow-clockwise btn-pure" onClick={refreshAll} />
|
<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 { useInView } from "react-intersection-observer"
|
||||||
import { useAtom, useAtomValue } from "jotai"
|
import { useAtom, useAtomValue } from "jotai"
|
||||||
import { useCallback } from "react"
|
import { useCallback } from "react"
|
||||||
import { currentSectionAtom, focusSourcesAtom } from "~/atoms"
|
import { currentSectionAtom, focusSourcesAtom, refetchSourceAtom } from "~/atoms"
|
||||||
|
|
||||||
export function Main() {
|
export function Main() {
|
||||||
const currentSection = useAtomValue(currentSectionAtom)
|
const currentSection = useAtomValue(currentSectionAtom)
|
||||||
@ -40,15 +40,32 @@ function NewsCard({ id, inView }: { id: SourceID, inView: boolean }) {
|
|||||||
const addFocusList = useCallback(() => {
|
const addFocusList = useCallback(() => {
|
||||||
setFocusSources(focusSources.includes(id) ? focusSources.filter(i => i !== id) : [...focusSources, id])
|
setFocusSources(focusSources.includes(id) ? focusSources.filter(i => i !== id) : [...focusSources, id])
|
||||||
}, [setFocusSources, focusSources, id])
|
}, [setFocusSources, focusSources, id])
|
||||||
const { isPending, error, isFetching, data, refetch } = useQuery({
|
const [refetchSource, setRefetchSource] = useAtom(refetchSourceAtom)
|
||||||
queryKey: [id],
|
const { isPending, error, isFetching, data } = useQuery({
|
||||||
queryFn: async () => {
|
queryKey: [id, refetchSource[id]],
|
||||||
const response = await fetch(`/api/${id}?latest`)
|
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
|
return await response.json() as SourceInfo
|
||||||
},
|
},
|
||||||
|
// refetch 时显示原有的数据
|
||||||
|
placeholderData: prev => prev,
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
enabled: inView,
|
enabled: inView,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const manualRefetch = useCallback(() => {
|
||||||
|
setRefetchSource(prev => ({
|
||||||
|
...prev,
|
||||||
|
[id]: Date.now(),
|
||||||
|
}))
|
||||||
|
}, [setRefetchSource, id])
|
||||||
|
|
||||||
if (isPending || !data) {
|
if (isPending || !data) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -84,8 +101,14 @@ function NewsCard({ id, inView }: { id: SourceID, inView: boolean }) {
|
|||||||
<span>
|
<span>
|
||||||
{formatTime(data!.updateTime)}
|
{formatTime(data!.updateTime)}
|
||||||
</span>
|
</span>
|
||||||
|
<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} />
|
<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>
|
||||||
</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 "virtual:uno.css"
|
||||||
import type { QueryClient } from "@tanstack/react-query"
|
import type { QueryClient } from "@tanstack/react-query"
|
||||||
import { Header } from "~/components/Header"
|
import { Header } from "~/components/Header"
|
||||||
|
import { useOnReload } from "~/hooks/useOnReload"
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<{
|
export const Route = createRootRouteWithContext<{
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
@ -21,6 +22,7 @@ export const Route = createRootRouteWithContext<{
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function RootComponent() {
|
export function RootComponent() {
|
||||||
|
useOnReload()
|
||||||
return (
|
return (
|
||||||
<div className="p-10">
|
<div className="p-10">
|
||||||
<Header />
|
<Header />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user