mirror of
https://github.com/apricote/Listory.git
synced 2026-02-07 02:07:03 +00:00
refactor(frontend): create useAsync hook for handling API data
This commit is contained in:
parent
42e8b886a0
commit
6a6ba493f6
2 changed files with 49 additions and 19 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { format, getTime } from "date-fns";
|
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 { Redirect } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Area,
|
Area,
|
||||||
|
|
@ -7,13 +7,14 @@ import {
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
TooltipProps,
|
||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
TooltipProps,
|
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { getListensReport } from "../api/api";
|
import { getListensReport } from "../api/api";
|
||||||
import { ListenReportItem } from "../api/entities/listen-report-item";
|
import { ListenReportItem } from "../api/entities/listen-report-item";
|
||||||
import { ListenReportOptions } from "../api/entities/listen-report-options";
|
import { ListenReportOptions } from "../api/entities/listen-report-options";
|
||||||
|
import { useAsync } from "../hooks/use-async";
|
||||||
import { useAuth } from "../hooks/use-auth";
|
import { useAuth } from "../hooks/use-auth";
|
||||||
|
|
||||||
export const ReportListens: React.FC = () => {
|
export const ReportListens: React.FC = () => {
|
||||||
|
|
@ -24,23 +25,14 @@ export const ReportListens: React.FC = () => {
|
||||||
timeStart: new Date("2020-05-01"),
|
timeStart: new Date("2020-05-01"),
|
||||||
timeEnd: new Date(),
|
timeEnd: new Date(),
|
||||||
});
|
});
|
||||||
const [report, setReport] = useState<ListenReportItem[]>([]);
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchData = useMemo(() => () => getListensReport(reportOptions), [
|
||||||
(async () => {
|
reportOptions,
|
||||||
setIsLoading(true);
|
]);
|
||||||
|
|
||||||
try {
|
const { value: report, pending: isLoading } = useAsync(fetchData, []);
|
||||||
const reportFromApi = await getListensReport(reportOptions);
|
|
||||||
setReport(reportFromApi);
|
const reportHasItems = !isLoading && report.length !== 0;
|
||||||
} catch (err) {
|
|
||||||
console.error("Error while fetching recent listens:", err);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [reportOptions, setReport, setIsLoading]);
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return <Redirect to="/" />;
|
return <Redirect to="/" />;
|
||||||
|
|
@ -81,12 +73,12 @@ export const ReportListens: React.FC = () => {
|
||||||
<div className="loader rounded-full border-8 h-64 w-64"></div>
|
<div className="loader rounded-full border-8 h-64 w-64"></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{report.length === 0 && (
|
{!reportHasItems && (
|
||||||
<div>
|
<div>
|
||||||
<p>Report is emtpy! :(</p>
|
<p>Report is emtpy! :(</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{report.length > 0 && (
|
{reportHasItems && (
|
||||||
<div className="w-full text-gray-700 mt-5">
|
<div className="w-full text-gray-700 mt-5">
|
||||||
<ReportGraph options={reportOptions} data={report} />
|
<ReportGraph options={reportOptions} data={report} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
38
frontend/src/hooks/use-async.tsx
Normal file
38
frontend/src/hooks/use-async.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
type UseAsync = <T>(
|
||||||
|
asyncFunction: () => Promise<T>,
|
||||||
|
initialValue: T
|
||||||
|
) => { pending: boolean; value: T; error: Error | null };
|
||||||
|
|
||||||
|
export const useAsync: UseAsync = <T extends any>(
|
||||||
|
asyncFunction: () => Promise<T>,
|
||||||
|
initialValue: T
|
||||||
|
) => {
|
||||||
|
const [pending, setPending] = useState(false);
|
||||||
|
const [value, setValue] = useState<T>(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 };
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue