From 6a6ba493f66fd89e0cf5bb22f7567b4f887cd30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Fri, 5 Jun 2020 01:22:04 +0200 Subject: [PATCH] refactor(frontend): create useAsync hook for handling API data --- frontend/src/components/ReportListens.tsx | 30 +++++++----------- frontend/src/hooks/use-async.tsx | 38 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 frontend/src/hooks/use-async.tsx diff --git a/frontend/src/components/ReportListens.tsx b/frontend/src/components/ReportListens.tsx index 8f80b93..87b0ab7 100644 --- a/frontend/src/components/ReportListens.tsx +++ b/frontend/src/components/ReportListens.tsx @@ -1,5 +1,5 @@ import { format, getTime } from "date-fns"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Redirect } from "react-router-dom"; import { Area, @@ -7,13 +7,14 @@ import { CartesianGrid, ResponsiveContainer, Tooltip, + TooltipProps, XAxis, YAxis, - TooltipProps, } from "recharts"; import { getListensReport } from "../api/api"; import { ListenReportItem } from "../api/entities/listen-report-item"; import { ListenReportOptions } from "../api/entities/listen-report-options"; +import { useAsync } from "../hooks/use-async"; import { useAuth } from "../hooks/use-auth"; export const ReportListens: React.FC = () => { @@ -24,23 +25,14 @@ export const ReportListens: React.FC = () => { timeStart: new Date("2020-05-01"), timeEnd: new Date(), }); - const [report, setReport] = useState([]); - const [isLoading, setIsLoading] = useState(true); - useEffect(() => { - (async () => { - setIsLoading(true); + const fetchData = useMemo(() => () => getListensReport(reportOptions), [ + reportOptions, + ]); - try { - const reportFromApi = await getListensReport(reportOptions); - setReport(reportFromApi); - } catch (err) { - console.error("Error while fetching recent listens:", err); - } finally { - setIsLoading(false); - } - })(); - }, [reportOptions, setReport, setIsLoading]); + const { value: report, pending: isLoading } = useAsync(fetchData, []); + + const reportHasItems = !isLoading && report.length !== 0; if (!user) { return ; @@ -81,12 +73,12 @@ export const ReportListens: React.FC = () => {
)} - {report.length === 0 && ( + {!reportHasItems && (

Report is emtpy! :(

)} - {report.length > 0 && ( + {reportHasItems && (
diff --git a/frontend/src/hooks/use-async.tsx b/frontend/src/hooks/use-async.tsx new file mode 100644 index 0000000..8811392 --- /dev/null +++ b/frontend/src/hooks/use-async.tsx @@ -0,0 +1,38 @@ +import React, { useState, useEffect, useCallback } from "react"; + +type UseAsync = ( + asyncFunction: () => Promise, + initialValue: T +) => { pending: boolean; value: T; error: Error | null }; + +export const useAsync: UseAsync = ( + asyncFunction: () => Promise, + initialValue: T +) => { + const [pending, setPending] = useState(false); + const [value, setValue] = useState(initialValue); + const [error, setError] = useState(null); + + // The execute function wraps asyncFunction and + // handles setting state for pending, value, and error. + // useCallback ensures the below useEffect is not called + // on every render, but only if asyncFunction changes. + const execute = useCallback(() => { + setPending(true); + setValue(initialValue); + setError(null); + return asyncFunction() + .then((response) => setValue(response)) + .catch((err) => setError(err)) + .finally(() => setPending(false)); + }, [asyncFunction]); + + // Call execute if we want to fire it right away. + // Otherwise execute can be called later, such as + // in an onClick handler. + useEffect(() => { + execute(); + }, [execute]); + + return { execute, pending, value, error }; +};