Эх сурвалжийг харах

clean up, using dequal to actually make callbacks work

Kirk Trombley 5 жил өмнө
parent
commit
b5f089f172

+ 1 - 0
package.json

@@ -3,6 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "dequal": "^1.0.0",
     "react": "^16.9.0",
     "react-dom": "^16.9.0",
     "react-scripts": "3.1.1"

+ 8 - 14
src/components/tiles/CommaFeed.jsx

@@ -1,27 +1,21 @@
-import React, { useState } from "react";
-import useJsonHealthCheck from "../../hooks/useJsonHealthCheck";
-import { UNKNOWN, OK, BAD, FAILED } from "../shared/StatusMessage";
+import React from "react";
+import { OK } from "../shared/StatusMessage";
 import Tile from "../shared/Tile";
+import useApi from "../../hooks/useApi";
 
-const update = 1000 * 60 * 20
+const ms = 1000 * 60 * 20;
+const fetchOptions = { credentials: "include" };
+const apiOptions = { ms, fetchOptions };
 const homepage = "https://kirkleon.ddns.net/commafeed/";
 const unreadLookup = "https://kirkleon.ddns.net/commafeed/rest/category/entries?id=all&limit=20&offset=0&order=desc&readType=unread";
 
 export default () => {
-  const [{ unread, health }, setState] = useState({ unread: null, health: UNKNOWN });
-  useJsonHealthCheck(
-    unreadLookup,
-    ({ entries }) => setState({ unread: entries.length, health: OK }),
-    () => setState({ unread: null, health: BAD }),
-    () => setState({ unread: null, health: FAILED }),
-    update,
-    "include",
-  );
+  const [health, data] = useApi(unreadLookup, apiOptions);
 
   return <Tile
     link={homepage}
     title="CommaFeed"
     health={health}
-    data={unread === null ? null : `${unread} items unread`}
+    data={health === OK ? `${data.entries.length} item(s) unread` : null}
   />
 }

+ 4 - 6
src/components/tiles/Gogs.jsx

@@ -1,17 +1,15 @@
 import React from "react";
 import Tile from "../shared/Tile";
-import useSimplePollHealth from "../../hooks/useSimplePollHealth";
+import useHealthPolling from "../../hooks/useHealthPolling";
 
+const gogsUrl = "https://kirkleon.ddns.net/gogs/";
 const update = 1000 * 60 * 20;
 
 export default () => {
-  const health = useSimplePollHealth({
-    url: "https://kirkleon.ddns.net/gogs/",
-    ms: update,
-  })
+  const health = useHealthPolling(gogsUrl, { ms: update })
 
   return <Tile
-    link="https://kirkleon.ddns.net/gogs/"
+    link={gogsUrl}
     title="Gogs"
     health={health}
   />

+ 5 - 11
src/components/tiles/Teamspeak.jsx

@@ -1,21 +1,15 @@
-import React, { useState } from "react";
-import useJsonHealthCheck from "../../hooks/useJsonHealthCheck";
-import { UNKNOWN, OK, BAD, FAILED } from "../shared/StatusMessage";
+import React from "react";
+import { OK } from "../shared/StatusMessage";
 import Tile from "../shared/Tile";
+import useApi from "../../hooks/useApi";
 
 export default () => {
-  const [{ users, health }, setState] = useState({ users: null, health: UNKNOWN });
-  useJsonHealthCheck(
-    "https://kirkleon.ddns.net/teamspeak/api",
-    ({ users }) => setState({ users: users.length, health: OK }),
-    () => setState({ users: null, health: BAD }),
-    () => setState({ users: null, health: FAILED }),
-  );
+  const [health, data] = useApi("https://kirkleon.ddns.net/teamspeak/api");
 
   return <Tile
     link="https://kirkleon.ddns.net/teamspeak/"
     title="Teamspeak Server"
     health={health}
-    data={users === null ? null : `${users} users online`}
+    data={health === OK ? `${data.users.length} user(s) online` : null}
   />
 }

+ 5 - 12
src/components/tiles/TerrAssumptions.jsx

@@ -1,22 +1,15 @@
-import React, { useState } from "react";
-import useJsonHealthCheck from "../../hooks/useJsonHealthCheck";
-import { UNKNOWN, OK, BAD, FAILED } from "../shared/StatusMessage";
+import React from "react";
 import Tile from "../shared/Tile";
+import useHealthPolling from "../../hooks/useHealthPolling";
 
+const taUrl = "https://kirkleon.ddns.net/terrassumptions/";
 const update = 1000 * 60 * 20;
 
 export default () => {
-  const [health, setHealth] = useState(UNKNOWN);
-  useJsonHealthCheck(
-    "https://kirkleon.ddns.net/terrassumptions/api",
-    () => setHealth(OK),
-    () => setHealth(BAD),
-    () => setHealth(FAILED),
-    update
-  );
+  const health = useHealthPolling(taUrl, { ms: update });
 
   return <Tile
-    link="https://kirkleon.ddns.net/terrassumptions/"
+    link={taUrl}
     title="TerrAssumptions"
     health={health}
   />

+ 37 - 0
src/hooks/useApi.js

@@ -0,0 +1,37 @@
+import { useRef, useState, useCallback } from "react";
+import dequal from "dequal";
+import useInterval from "./useInterval";
+import { UNKNOWN, OK, BAD, FAILED } from "../components/shared/StatusMessage";
+
+export default (
+  url,
+  {
+    ms = 20000,
+    fetchOptions = {},
+  } = {},
+) => {
+  const fetchOptsRef = useRef({});
+  if (!dequal(fetchOptsRef.current, fetchOptions)) {
+    fetchOptsRef.current = fetchOptions;
+  }
+
+  const [result, setResult] = useState([UNKNOWN, null]);
+  const pollApi = useCallback(async () => {
+    try {
+      const res = await fetch(url, fetchOptsRef.current);
+      const js = await res.json();
+      if (res.ok) {
+        setResult([OK, js]);
+      } else {
+        setResult([BAD, js]);
+      }
+    }
+    catch (err) {
+      setResult([FAILED, err]);
+    }
+  }, [url]);
+
+  useInterval(pollApi, ms);
+
+  return result;
+}

+ 12 - 0
src/hooks/useEffectDequal.js

@@ -0,0 +1,12 @@
+import { useEffect, useRef } from "react";
+import dequal from "dequal";
+
+export default (effect, deps) => {
+  const ref = useRef();
+
+  if (!dequal(ref.current, deps)) {
+    ref.current = deps;
+  }
+
+  useEffect(effect, ref.current);
+}

+ 36 - 0
src/hooks/useHealthPolling.js

@@ -0,0 +1,36 @@
+import { useRef, useState, useCallback } from "react";
+import dequal from "dequal";
+import { UNKNOWN, OK, BAD, FAILED } from "../components/shared/StatusMessage";
+import useInterval from "./useInterval";
+
+export default (
+  url,
+  {
+    ms = 20000,
+    fetchOptions = {},
+  } = {}
+) => {
+  const fetchOptsRef = useRef({});
+  if (!dequal(fetchOptsRef.current, fetchOptions)) {
+    fetchOptsRef.current = fetchOptions;
+  }
+
+  const [status, setStatus] = useState(UNKNOWN);
+
+  const checkServer = useCallback(async () => {
+    try {
+      const res = await fetch(url, fetchOptsRef.current);
+      if (res.ok) {
+        setStatus(OK);
+      } else {
+        setStatus(BAD);
+      }
+    } catch (err) {
+      setStatus(FAILED);
+    }
+  }, [url]);
+
+  useInterval(checkServer, ms);
+
+  return status;
+}

+ 11 - 0
src/hooks/useInterval.js

@@ -0,0 +1,11 @@
+import { useEffect } from "react";
+
+export default (effect, ms) => {
+  useEffect(() => {
+    effect();
+    
+    const interval = setInterval(effect, ms);
+
+    return () => clearInterval(interval);
+  }, [effect, ms]);
+};

+ 0 - 34
src/hooks/useSimplePollHealth.js

@@ -1,34 +0,0 @@
-import { useState, useEffect, useCallback } from "react";
-import { UNKNOWN, OK, BAD, FAILED } from "../components/shared/StatusMessage";
-
-export default ({
-  url,
-  ms = 20000,
-  fetchOptions = {},
-}) => {
-  const [status, setStatus] = useState(UNKNOWN);
-  const checkServer = useCallback(async () => {
-    console.log(`Polling ${url}`);
-    try {
-      const res = await fetch(url, fetchOptions);
-      if (res.ok) {
-        setStatus(OK);
-      } else {
-        setStatus(BAD);
-      }
-    } catch (err) {
-      console.log(err);
-      setStatus(FAILED);
-    }
-  }, [url, fetchOptions]);
-
-  useEffect(() => {
-    checkServer();
-    
-    const interval = setInterval(checkServer, ms);
-
-    return () => clearInterval(interval);
-  }, [ms, checkServer]);
-
-  return status;
-}

+ 7 - 2
yarn.lock

@@ -3080,6 +3080,11 @@ depd@~1.1.2:
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
   integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
 
+dequal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.0.tgz#41c6065e70de738541c82cdbedea5292277a017e"
+  integrity sha512-/Nd1EQbQbI9UbSHrMiKZjFLrXSnU328iQdZKPQf78XQI6C+gutkFUeoHpG5J08Ioa6HeRbRNFpSIclh1xyG0mw==
+
 des.js@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
@@ -7872,7 +7877,7 @@ react-dev-utils@^9.0.3:
     strip-ansi "5.2.0"
     text-table "0.2.0"
 
-react-dom@16.9.0:
+react-dom@^16.9.0:
   version "16.9.0"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0.tgz#5e65527a5e26f22ae3701131bcccaee9fb0d3962"
   integrity sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==
@@ -7953,7 +7958,7 @@ react-scripts@3.1.1:
   optionalDependencies:
     fsevents "2.0.7"
 
-react@16.9.0:
+react@^16.9.0:
   version "16.9.0"
   resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa"
   integrity sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==