diff --git a/app/soapbox/features/ui/components/trends-panel.tsx b/app/soapbox/features/ui/components/trends-panel.tsx index 49b68887d..ee0d8d763 100644 --- a/app/soapbox/features/ui/components/trends-panel.tsx +++ b/app/soapbox/features/ui/components/trends-panel.tsx @@ -1,40 +1,32 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; -import { fetchTrends } from 'soapbox/actions/trends'; import Hashtag from 'soapbox/components/hashtag'; import { Widget } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import useTrends from 'soapbox/queries/trends'; interface ITrendsPanel { limit: number } const TrendsPanel = ({ limit }: ITrendsPanel) => { - const dispatch = useDispatch(); - - const trends = useAppSelector((state) => state.trends.items); + const { data: trends, isFetching } = useTrends(); const sortedTrends = React.useMemo(() => { - return trends.sort((a, b) => { + return trends?.sort((a, b) => { const num_a = Number(a.getIn(['history', 0, 'accounts'])); const num_b = Number(b.getIn(['history', 0, 'accounts'])); return num_b - num_a; }).slice(0, limit); }, [trends, limit]); - React.useEffect(() => { - dispatch(fetchTrends()); - }, []); - - if (sortedTrends.isEmpty()) { + if (trends?.length === 0 || isFetching) { return null; } return ( }> - {sortedTrends.map((hashtag) => ( + {sortedTrends?.slice(0, limit).map((hashtag) => ( ))} diff --git a/app/soapbox/queries/__tests__/trends.test.ts b/app/soapbox/queries/__tests__/trends.test.ts new file mode 100644 index 000000000..52f319d26 --- /dev/null +++ b/app/soapbox/queries/__tests__/trends.test.ts @@ -0,0 +1,42 @@ +import { __stub } from 'soapbox/api'; +import { renderHook, waitFor } from 'soapbox/jest/test-helpers'; + +import useTrends from '../trends'; + +describe('useTrends', () => { + describe('with a successul query', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/trends') + .reply(200, [ + { name: '#golf', url: 'https://example.com' }, + { name: '#tennis', url: 'https://example.com' }, + ]); + }); + }); + + it('is successful', async() => { + const { result } = renderHook(() => useTrends()); + + await waitFor(() => expect(result.current.isFetching).toBe(false)); + + expect(result.current.data?.length).toBe(2); + }); + }); + + describe('with an unsuccessul query', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/trends').networkError(); + }); + }); + + it('is successful', async() => { + const { result } = renderHook(() => useTrends()); + + await waitFor(() => expect(result.current.isFetching).toBe(false)); + + expect(result.current.error).toBeDefined(); + }); + }); +}); diff --git a/app/soapbox/queries/trends.ts b/app/soapbox/queries/trends.ts new file mode 100644 index 000000000..8613719b0 --- /dev/null +++ b/app/soapbox/queries/trends.ts @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query'; + +import { fetchTrendsSuccess } from 'soapbox/actions/trends'; +import { useApi, useAppDispatch } from 'soapbox/hooks'; +import { normalizeTag } from 'soapbox/normalizers'; + +import type { Tag } from 'soapbox/types/entities'; + +export default function useTrends() { + const api = useApi(); + const dispatch = useAppDispatch(); + + const getTrends = async() => { + const { data } = await api.get('/api/v1/trends'); + + dispatch(fetchTrendsSuccess(data)); + + const normalizedData = data.map((tag) => normalizeTag(tag)); + return normalizedData; + }; + + const result = useQuery(['trends'], getTrends, { + placeholderData: [], + staleTime: 600000, // 10 minutes + }); + + return result; +} \ No newline at end of file