From 264e571b0ca34d24f4997c5864a43d4f475e14dc Mon Sep 17 00:00:00 2001 From: Galen Guyer Date: Wed, 13 Apr 2022 19:10:44 -0400 Subject: full version 2 re-write --- src/App.css | 44 ++++++-- src/App.js | 229 ----------------------------------------- src/App.jsx | 142 +++++++++++++++++++++++++ src/App.test.js | 8 -- src/Card.css | 10 -- src/Card.js | 28 ----- src/GoatCounter.js | 26 ----- src/History.js | 89 ---------------- src/HistoryTable.js | 40 ------- src/MainPage.js | 166 ----------------------------- src/components/Card.css | 43 ++++++++ src/components/Card.jsx | 25 +++++ src/components/GoatCounter.jsx | 26 +++++ src/index.css | 22 ++-- src/index.js | 22 ---- src/main.jsx | 10 ++ src/pages/Graph.css | 4 + src/pages/Graph.jsx | 96 +++++++++++++++++ src/pages/Index.css | 22 ++++ src/pages/Index.jsx | 66 ++++++++++++ src/reportWebVitals.js | 13 --- src/setupTests.js | 5 - src/useFetch.js | 17 +++ 23 files changed, 496 insertions(+), 657 deletions(-) delete mode 100644 src/App.js create mode 100644 src/App.jsx delete mode 100644 src/App.test.js delete mode 100644 src/Card.css delete mode 100644 src/Card.js delete mode 100644 src/GoatCounter.js delete mode 100644 src/History.js delete mode 100644 src/HistoryTable.js delete mode 100644 src/MainPage.js create mode 100644 src/components/Card.css create mode 100644 src/components/Card.jsx create mode 100644 src/components/GoatCounter.jsx delete mode 100644 src/index.js create mode 100644 src/main.jsx create mode 100644 src/pages/Graph.css create mode 100644 src/pages/Graph.jsx create mode 100644 src/pages/Index.css create mode 100644 src/pages/Index.jsx delete mode 100644 src/reportWebVitals.js delete mode 100644 src/setupTests.js create mode 100644 src/useFetch.js (limited to 'src') diff --git a/src/App.css b/src/App.css index 78106d4..50e39d9 100644 --- a/src/App.css +++ b/src/App.css @@ -1,15 +1,37 @@ .App { - text-align: center; - padding-bottom: 2rem; - padding-top: 1rem; - width: 90%; - margin-left: auto; - margin-right: auto; + text-align: center; + margin: auto; + max-width: 90vw; + + position: relative; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.App h1 { + font-size: 2.4em; + margin: 18px 0px 8px 0px; +} +.Updated .Latest { + font-size: 1.3em; + margin: 0px; +} +.Updated .Prior { + margin: 6px; + color: #666; +} + +.App a { + text-decoration: none; + color: #000; } -.Section { - display: flex; - justify-content: center; - margin-left: auto; - margin-right: auto; +.BlueLink { + color: #008 !important; } + +footer { + margin-top: auto; + margin-bottom: 20px; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 3edd599..0000000 --- a/src/App.js +++ /dev/null @@ -1,229 +0,0 @@ -import React from "react"; -import useSWR from "swr"; -import { DateTime } from "luxon"; -import { BrowserRouter, Route, Switch, Link } from "react-router-dom"; -import MainPage from "./MainPage"; -import History from "./History"; -import HistoryTable from "./HistoryTable"; -import "./App.css"; - -const url = "https://ritcoviddashboard.com/api/v0/history"; - -function App() { - let { data: rawData, error: error } = useSWR(url); - - const [timeDifference, setTimeDifference] = React.useState(1); - const [showAllTime, setShowAllTime] = React.useState(false); - - if (error) - return ( -
-

RIT Covid Dashboard

-

An error occurred

-
- ); - if (!rawData) - return ( -
-

RIT Covid Dashboard

-

Loading latest data...

-
- ); - - // rawData = rawData.slice(0, 177); - let data = rawData; - console.log(data.length); - const local = DateTime.local().zoneName; - const semesterStart = DateTime.fromISO("2021-01-01"); - // if (!showAllTime) { - // data = rawData.filter((d) => { - // let date = DateTime.fromSQL(d.last_updated, { zone: "UTC" }).setZone(local); - // return date > semesterStart; - // }); - // const last = rawData[rawData.length - data.length - 1]; - // data = data.map((d) => { - // return { - // alert_level: d.alert_level, - // beds_available: d.beds_available, - // isolation_off_campus: d.isolation_off_campus, - // isolation_on_campus: d.isolation_on_campus, - // last_updated: d.last_updated, - // new_staff: d.new_staff, - // new_students: d.new_students, - // quarantine_off_campus: d.quarantine_off_campus, - // quarantine_on_campus: d.quarantine_on_campus, - // tests_administered: d.tests_administered - last.tests_administered, - // total_staff: d.total_staff - last.total_staff, - // total_students: d.total_students - last.total_students, - // }; - // }); - // } - - const latest = data[data.length - 1]; - const prior = data[data.length - (1 + timeDifference)]; - const lastUpdate = DateTime.fromSQL(latest.last_updated).setZone(local); - const priorUpdate = DateTime.fromSQL(prior.last_updated).setZone(local); - // let positiveCases = []; - // for (let i = 5; i < data.length; i++) { - // positiveCases.push({ - // date: data[i].last_updated, - // value: ( - // ((data[i].total_students - data[i - 5].total_students) * 100) / - // (data[i].tests_administered - data[i - 5].tests_administered) - // ).toFixed(1), - // }); - // } - // positiveCases = positiveCases.filter((o) => o.value > 0 && o.value <= 100); - return ( - -
-

- RIT Covid Dashboard -

-

- Last Updated:{" "} - {lastUpdate.toLocaleString({ - weekday: "long", - month: "long", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - })} -

-

- Prior Update:{" "} - {priorUpdate.toLocaleString({ - weekday: "long", - month: "long", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - })}{" "} - ({timeDifference == 1 ? "one day ago" : timeDifference == 5 ? "one week ago" : "two weeks ago"}) -

- {/* -   - */} -
- - - - - - { - return { value: d.total_students, date: d.last_updated }; - })} - /> - - - { - return { value: d.total_staff, date: d.last_updated }; - })} - /> - - - { - return { value: d.new_students, date: d.last_updated }; - })} - /> - - - { - return { value: d.new_staff, date: d.last_updated }; - })} - /> - - {/* - { - return { value: d.quarantine_on_campus, date: d.last_updated }; - })} - /> - - - { - return { value: d.quarantine_off_campus, date: d.last_updated }; - })} - /> - - - - { - return { value: d.isolation_on_campus, date: d.last_updated }; - })} - /> - - - { - return { value: d.isolation_off_campus, date: d.last_updated }; - })} - /> - - - { - return { value: d.tests_administered, date: d.last_updated }; - })} - /> - - - - - - { - return { value: d.beds_available, date: d.last_updated }; - })} - /> - */} - -
-

- By Galen Guyer. Source available on{" "} - - GitHub - {" "} - ( - - Report Issue - - ) -

-

- - API Documentation - -

-
-
- ); -} - -export default App; diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..af8f570 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,142 @@ +import { BrowserRouter, Routes, Route, Link } from "react-router-dom"; +import { useState } from "react"; +import useFetch from "./useFetch"; +import Index from "./pages/Index"; +import { DateTime } from "luxon"; +import "./App.css"; +import Graph from "./pages/Graph"; + +const App = () => { + const url = localStorage.getItem("url") ?? "https://ritcoviddashboard.com/api/v0/history"; + + const response = useFetch(url); + + const [timeDifference, setTimeDifference] = useState(1); + const local = DateTime.local().zoneName; + + let data = response.data ?? []; + + const latest = response.loading ? null : data[data.length - 1]; + const prior = response.loading ? null : data[data.length - (1 + timeDifference)]; + + const lastUpdate = response.loading ? null : DateTime.fromSQL(latest.last_updated).setZone(local); + const priorUpdate = response.loading ? null : DateTime.fromSQL(prior.last_updated).setZone(local); + + return ( + +
+
+ +

RIT COVID Dashboard

+ + +
+ + + } + > + + } + > + + } + > + + } + > + } /> + + +
+
+ ); +}; + +const Updated = (props) => { + const { loading, lastUpdate, priorUpdate, timeDifference } = props; + if (loading) { + return
; + } + + return ( +
+
+ Last Updated:{" "} + {lastUpdate.toLocaleString({ + weekday: "long", + month: "long", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + })} +
+
+ Prior Update:{" "} + {priorUpdate.toLocaleString({ + weekday: "long", + month: "long", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + })}{" "} + ({timeDifference == 1 ? "one weekday ago" : timeDifference == 5 ? "one week ago" : "two weeks ago"}) +
+
+ ); +}; + +export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 7c46384..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import App from "./App"; - -test("renders learn react link", () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/Card.css b/src/Card.css deleted file mode 100644 index 785b3e5..0000000 --- a/src/Card.css +++ /dev/null @@ -1,10 +0,0 @@ -.Card { - padding: 16px; - width: 40%; -} - -@media screen and (min-width: 768px) { - .Card { - width: 20%; - } -} diff --git a/src/Card.js b/src/Card.js deleted file mode 100644 index 14b4282..0000000 --- a/src/Card.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import "./Card.css"; - -const Card = (props) => { - let diff = props.diff.toString(); - if (diff.charAt(0) != "-") { - diff = "+" + diff; - } - - return ( - -
-

- - {props.latest}{" "} - - - ({diff}) - -

-

{props.name}

-
- - ); -}; - -export default Card; diff --git a/src/GoatCounter.js b/src/GoatCounter.js deleted file mode 100644 index 07a768a..0000000 --- a/src/GoatCounter.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -class GoatCounter extends React.Component { - componentDidMount() { - window.counter = "https://rcd.goatcounter.com/count"; - const script = window.document.createElement("script"); - script.async = 1; - script.src = "https://gc.zgo.at/count.js"; - script.id = "goatcounter"; - script.setAttribute("data-goatcounter", "https://rcd.goatcounter.com/count"); - (window.document.head || window.document.body).appendChild(script); - } - - componentWillUnmount() { - const script = window.document.getElementById("goatcounter"); - if (script) { - script.parentNode.removeChild(script); - } - } - - render() { - return null; - } -} - -export default GoatCounter; diff --git a/src/History.js b/src/History.js deleted file mode 100644 index 58085ac..0000000 --- a/src/History.js +++ /dev/null @@ -1,89 +0,0 @@ -import { React, PureComponent } from "react"; -import { DateTime } from "luxon"; -import { - BarChart, - Bar, - LineChart, - Line, - CartesianGrid, - XAxis, - YAxis, - Tooltip, - ReferenceLine, - ResponsiveContainer, - Label, -} from "recharts"; -import GoatCounter from "./GoatCounter"; - -const History = (props) => { - const offset = DateTime.fromSQL(props.data[0].date, { zone: "UTC" }).setZone(DateTime.local().zoneName).toSeconds(); - const data = props.data.map((d) => { - return { - value: d.value, - date: DateTime.fromSQL(d.date, { zone: "UTC" }).setZone(DateTime.local().zoneName).toSeconds(), - }; - }); - - if (process.env.NODE_ENV == "development") { - console.log(data); - } - - return ( - <> -

{props.name}

- - - {/* */} - - - } - height={90} - /> - - - - - - ); -}; - -class CustomizedAxisTick extends PureComponent { - render() { - const { x, y, payload } = this.props; - - return ( - - - {DateTime.fromSeconds(payload.value).toLocaleString()} - - - ); - } -} - -const CustomTooltip = ({ active, payload, label }) => { - if (active) { - return ( -
-

- {DateTime.fromSeconds(label).toLocaleString({ weekday: "long", month: "long", day: "2-digit" })} -

-

{payload[0].value}

-
- ); - } - return null; -}; - -export default History; diff --git a/src/HistoryTable.js b/src/HistoryTable.js deleted file mode 100644 index fb27f0e..0000000 --- a/src/HistoryTable.js +++ /dev/null @@ -1,40 +0,0 @@ -import { React } from "react"; -import { DateTime } from "luxon"; -import GoatCounter from "./GoatCounter"; - -const HistoryTable = (props) => { - const data = props.data; - console.log(data); - let table = ( - - - - - - - {data.map((element) => { - return ( - - - - - ); - })} - -
DatePositive Case Rate
- {DateTime.fromSQL(element.date, { zone: "UTC" }) - .setZone(DateTime.local().zoneName) - .toLocaleString({ weekday: "long", month: "long", day: "2-digit" })} - {element.value}%
- ); - - return ( - <> -

{props.name}

- {table} - - - ); -}; - -export default HistoryTable; diff --git a/src/MainPage.js b/src/MainPage.js deleted file mode 100644 index b663521..0000000 --- a/src/MainPage.js +++ /dev/null @@ -1,166 +0,0 @@ -import React from "react"; -import Card from "./Card"; -import GoatCounter from "./GoatCounter"; - -const MainPage = (props) => { - const data = props.data; - const latest = data[data.length - 1]; - const prior = data[data.length - (1 + props.timeDifference)]; - // const priorPrior = data[Math.max(0, data.length - (1 + props.timeDifference * 2))]; - - // const positiveTestRate = Math.max( - // 0, - // Math.min( - // 100, - // ((latest.total_students - prior.total_students) * 100) / - // (latest.tests_administered - prior.tests_administered) - // ) - // ).toFixed(1); - // const priorPositiveTestRate = Math.max( - // 0, - // Math.min( - // 100, - // ((prior.total_students - priorPrior.total_students) * 100) / - // (prior.tests_administered - priorPrior.tests_administered) - // ) - // ).toFixed(1); - - return ( - <> - {/*

- Alert Level: {latest.alert_level.charAt(0).toUpperCase() + latest.alert_level.slice(1)} -

-
- (Prior Alert Level: {prior.alert_level.charAt(0).toUpperCase() + prior.alert_level.slice(1)}) -
-
*/} -

- This dashboard has been refreshed for the new semester. Historical data from the 2020-2021 school year is - available at 2020.ritcoviddashboard.com. Data from the Fall 2021 - semester is available at 2021.ritcoviddashboard.com. -

-
-
-

- {/* Total Positive Cases Since {props.showAllTime ? "August 19 (First Day of Classes)" : "January 1"} */} - Total Positive Cases Since January 10 (First Day of Classes) -

-
- - -
-
-
-
-

New Positive Cases From Past 14 Days

-
- - -
-
- {/*
-
-

Number of Students in Quarantine

-
- Quarantine separates and restricts the movement of people who were exposed to a contagious disease - to see if they become sick. -
-
- - -
-
-
-
-

Number of Students in Isolation

-
- Isolation separates sick people with a contagious disease from people who are not sick. -
-
- - -
-
-
-
-

Tests

-
- Positive Test Rate is calculated using the difference in total cases divided by the difference in - tests administed for the selected time frame (one day or one week). The daily positive test rate - fluctuates wildly and should be taken with caution, while the weekly positive test rate is far more - stable and useful. -
-
- - -
-
-
-
-

Quarantine/Isolation Bed Availability On-campus

-
- -
-
*/} - - - ); -}; - -export default MainPage; diff --git a/src/components/Card.css b/src/components/Card.css new file mode 100644 index 0000000..cca784b --- /dev/null +++ b/src/components/Card.css @@ -0,0 +1,43 @@ +.cardLink { + text-decoration: none; + color: black; +} + +.Card { + text-decoration: none; + color: #000; + border: 2px solid #fbd38d; + border-radius: 12px; + padding: 0px 40px; + margin: 24px; +} + +@media screen and (max-width: 600px) { + .Card { + padding: 0px 20px; + margin: 18px; + } +} + +.Latest { + font-size: 1.8em; +} + +.Diff { + color: #666; +} + +.Card:hover { + background-color: #ffc869; + color: #fff; +} +.Card:hover .Diff { + color: #fff; +} + +.animate { + -moz-transition: color,background-color 0.3s; + -webkit-transition: color,background-color 0.3s; + -ms-transition: color,background-color 0.3s; + transition: color,background-color 0.3s; +} diff --git a/src/components/Card.jsx b/src/components/Card.jsx new file mode 100644 index 0000000..95bd583 --- /dev/null +++ b/src/components/Card.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import "./Card.css"; + +const Card = (props) => { + let diff = props.diff.toString(); + if (diff.charAt(0) != "-") { + diff = "+" + diff; + } + + return ( + +
+

+ {props.latest} ({diff}) +

+

+ {props.name} +

+
+ + ); +}; + +export default Card; diff --git a/src/components/GoatCounter.jsx b/src/components/GoatCounter.jsx new file mode 100644 index 0000000..07a768a --- /dev/null +++ b/src/components/GoatCounter.jsx @@ -0,0 +1,26 @@ +import React from "react"; + +class GoatCounter extends React.Component { + componentDidMount() { + window.counter = "https://rcd.goatcounter.com/count"; + const script = window.document.createElement("script"); + script.async = 1; + script.src = "https://gc.zgo.at/count.js"; + script.id = "goatcounter"; + script.setAttribute("data-goatcounter", "https://rcd.goatcounter.com/count"); + (window.document.head || window.document.body).appendChild(script); + } + + componentWillUnmount() { + const script = window.document.getElementById("goatcounter"); + if (script) { + script.parentNode.removeChild(script); + } + } + + render() { + return null; + } +} + +export default GoatCounter; diff --git a/src/index.css b/src/index.css index 868b6c6..c4c1ceb 100644 --- a/src/index.css +++ b/src/index.css @@ -1,15 +1,17 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", - "Droid Sans", "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } + +* { + font-weight: 400; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 4c1dc74..0000000 --- a/src/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { SWRConfig } from "swr"; -import "./index.css"; -import App from "./App"; -import reportWebVitals from "./reportWebVitals"; - -const fetcher = (...args) => fetch(...args).then((res) => res.json()); - -ReactDOM.render( - - - - - , - document.getElementById("root") -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..9af0bb6 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +) diff --git a/src/pages/Graph.css b/src/pages/Graph.css new file mode 100644 index 0000000..c28c8bd --- /dev/null +++ b/src/pages/Graph.css @@ -0,0 +1,4 @@ +.Title { + font-size: 2.0em; + margin-top: 24px; +} diff --git a/src/pages/Graph.jsx b/src/pages/Graph.jsx new file mode 100644 index 0000000..6754905 --- /dev/null +++ b/src/pages/Graph.jsx @@ -0,0 +1,96 @@ +import { DateTime } from "luxon"; +import { + BarChart, + Bar, + LineChart, + Line, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + ReferenceLine, + ResponsiveContainer, + Label, +} from "recharts"; +import GoatCounter from "../components/GoatCounter"; +import "./Graph.css"; + +const Graph = (props) => { + const { name, response, dataKey, timeDifference } = props; + const { data, loading, error } = response; + + if (loading) { + return
; + } + + const eventStyle = { fill: "#767676" }; + + const parsed = data.map((d) => { + return { + date: DateTime.fromSQL(d["last_updated"], { zone: "UTC" }).setZone(DateTime.local().zoneName).toSeconds(), + value: d[dataKey], + }; + }); + + return ( +
+
{name}
+ 600 ? 750 : window.innerWidth * 0.9} + height={500} + margin={{ top: 15, right: 30, left: 0, bottom: 5 }} + data={parsed} + > + + + + {} + + } + height={90} + /> + + + + +
+ ); +}; + +const CustomizedAxisTick = ({ x, y, payload }) => { + return ( + + + {DateTime.fromSeconds(payload.value).toLocaleString()} + + + ); +}; + +const CustomTooltip = ({ active, payload, label }) => { + if (active) { + return ( +
+

+ {DateTime.fromSeconds(label).toLocaleString({ weekday: "long", month: "long", day: "2-digit" })} +

+

{payload[0].value}

+
+ ); + } + return null; +}; + +export default Graph; diff --git a/src/pages/Index.css b/src/pages/Index.css new file mode 100644 index 0000000..5476eb1 --- /dev/null +++ b/src/pages/Index.css @@ -0,0 +1,22 @@ +.Message { + margin-top: 24px; + font-size: 1.4em; +} + +@media screen and (max-width: 600px) { + .Message { + display: none; + } +} + +.Section .Title { + text-align: center; + font-size: 1.6em; + flex-basis: 100%; + margin-top: 48px; +} + +.Cards { + display: flex; + justify-content: center; +} diff --git a/src/pages/Index.jsx b/src/pages/Index.jsx new file mode 100644 index 0000000..3beb6fd --- /dev/null +++ b/src/pages/Index.jsx @@ -0,0 +1,66 @@ +import Card from "../components/Card"; +import GoatCounter from "../components/GoatCounter"; +import "./Index.css"; + +const Index = (props) => { + const response = props.response; + if (response.loading) { + return
Loading...
; + } + + const data = response.data; + + const latest = data[data.length - 1]; + const prior = data[data.length - (1 + props.timeDifference)]; + + return ( +
+
+
+ This dashboard has been refreshed for the new semester. Historical data from the 2020-2021 school + year is available at 2020.ritcoviddashboard.com. Data + from the Fall 2021 semester is available at{" "} + 2021.ritcoviddashboard.com. +
+
+
+
Total Positive Cases Since January 10 (First Day of Classes)
+
+ + +
+
+ +
+
New Positive Cases From Past 14 Days
+
+ + +
+
+ +
+ ); +}; + +export default Index; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 7dc6b90..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = (onPerfEntry) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 1dd407a..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom"; diff --git a/src/useFetch.js b/src/useFetch.js new file mode 100644 index 0000000..d098e2f --- /dev/null +++ b/src/useFetch.js @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export default function useFetch(url, options) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetch(url, options) + .then((resp) => resp.json()) + .then((resp) => setData(resp)) + .catch((err) => setError(err)) + .finally(() => setLoading(false)); + }, []); + + return { data, loading, error }; +} -- cgit v1.2.3