From 3ac74ef7808fb2ac15240885ba2d3bc5eeab96ad Mon Sep 17 00:00:00 2001
From: dm1sh <me@dmitriy.icu>
Date: Fri, 16 Jul 2021 04:30:49 +0500
Subject: [PATCH] Switched from array to hash table as book list, some naming
 errors fixed

---
 src/context.tsx                |  3 ++-
 src/hooks/useLibrary.ts        | 40 ++++++++++++++++++++--------------
 src/pages/Bookshelf/index.tsx  |  2 +-
 src/pages/UploadForm/index.tsx |  2 +-
 src/{ => utils}/api.ts         |  9 +++++---
 src/utils/localStorage.ts      | 38 +++++++++++++++-----------------
 6 files changed, 52 insertions(+), 42 deletions(-)
 rename src/{ => utils}/api.ts (84%)

diff --git a/src/context.tsx b/src/context.tsx
index 24ff9af..f5c5243 100644
--- a/src/context.tsx
+++ b/src/context.tsx
@@ -3,8 +3,9 @@ import { useLibrary, UseLibraryReturnTuple } from "./hooks/useLibrary";
 import { IBook } from "./types/book";
 
 export const BookListContext = React.createContext<UseLibraryReturnTuple>([
-  [],
+  {},
   (book: IBook) => {},
+  [],
 ]);
 
 export const BookListContextProvider: React.FC = ({ children }) => {
diff --git a/src/hooks/useLibrary.ts b/src/hooks/useLibrary.ts
index a0a8d34..ca295f5 100644
--- a/src/hooks/useLibrary.ts
+++ b/src/hooks/useLibrary.ts
@@ -1,38 +1,46 @@
 import { useEffect, useState } from "react";
 import { IBook } from "~/types/book";
 import {
-  getBookList,
-  getTitleList,
-  setBook,
-  updateTitleList,
+  getBookHT,
+  getHashList,
+  saveBook,
+  updateHashList,
 } from "~/utils/localStorage";
 
 export type AddBookFT = (book: IBook) => void;
 
-export type UseLibraryReturnTuple = [IBook[], AddBookFT];
+export type UseLibraryReturnTuple = [
+  Record<string, IBook>,
+  AddBookFT,
+  string[]
+];
 
 export const useLibrary = (): UseLibraryReturnTuple => {
-  const [bookList, setBookList] = useState<IBook[]>([]);
-  const [titleList, setTitleList] = useState<string[]>([]);
+  const [library, setLibrary] = useState<Record<string, IBook>>({});
+  const [hashList, setHashList] = useState<string[]>([]);
 
   const addBook: AddBookFT = (book) => {
     const key = book.hash || Date.now().toString();
-    if (key && !titleList.includes(key)) {
-      setTitleList([key, ...titleList]);
-      setBookList([book, ...bookList]);
+    if (key && !hashList.includes(key)) {
+      setHashList([key, ...hashList]);
+      setLibrary((prev) => ({ [key]: book, ...prev }));
 
-      setBook(key, book);
+      saveBook(key, book);
     }
   };
 
   useEffect(() => {
-    const receivedTitleList = getTitleList();
+    const receivedHashList = getHashList();
 
-    setTitleList(receivedTitleList);
-    setBookList(getBookList(receivedTitleList));
+    setHashList(receivedHashList);
+    setLibrary(getBookHT(receivedHashList));
   }, []);
 
-  useEffect(() => updateTitleList(titleList), [titleList]);
+  useEffect(() => updateHashList(hashList), [hashList]);
 
-  return [bookList, addBook];
+  useEffect(() => {
+    console.log(library);
+  }, [library]);
+
+  return [library, addBook, hashList];
 };
diff --git a/src/pages/Bookshelf/index.tsx b/src/pages/Bookshelf/index.tsx
index 697fe4f..e337957 100644
--- a/src/pages/Bookshelf/index.tsx
+++ b/src/pages/Bookshelf/index.tsx
@@ -13,7 +13,7 @@ export const Bookshelf = () => {
     <div className={styles.container}>
       <div className={styles.scrollContainer}>
         <AddBook />
-        {books.map((book, index) => (
+        {Object.values(books).map((book, index) => (
           <BookItem key={book.hash} {...book} />
         ))}
       </div>
diff --git a/src/pages/UploadForm/index.tsx b/src/pages/UploadForm/index.tsx
index 7c5f5fc..824211a 100644
--- a/src/pages/UploadForm/index.tsx
+++ b/src/pages/UploadForm/index.tsx
@@ -3,7 +3,7 @@ import { useLocation } from "wouter";
 
 import plusIcon from "~/assets/plus.svg";
 import styles from "./UploadForm.module.css";
-import { submitFile, validateResponse, validState } from "~/api";
+import { submitFile, validateResponse, validState } from "~/utils/api";
 import { BookListContext } from "~/context";
 
 export const UploadForm = () => {
diff --git a/src/api.ts b/src/utils/api.ts
similarity index 84%
rename from src/api.ts
rename to src/utils/api.ts
index 37e03c0..b8bb602 100644
--- a/src/api.ts
+++ b/src/utils/api.ts
@@ -1,6 +1,6 @@
 import { IBook, requiredBookProps } from "~/types/book";
 
-import { API_URL } from "./constants";
+import { API_URL } from "~/constants";
 
 export const validState = (file: File | undefined): file is File => {
   if (!file) throw new Error("Book file is required. Please, attach one");
@@ -42,8 +42,11 @@ export const submitFile = async (
 export const validateResponse = (content: unknown): content is IBook => {
   if (content && typeof content === "object")
     for (const key of requiredBookProps)
-      if (!(key in content))
-        throw new Error(`${key} is not specified in server response`);
+      if (!(key in content)) {
+        if (import.meta.env.NODE_ENV === "development")
+          console.log(`${key} is not specified in server response`);
+        return false;
+      }
 
   return true;
 };
diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts
index d454a84..a5ffd91 100644
--- a/src/utils/localStorage.ts
+++ b/src/utils/localStorage.ts
@@ -1,33 +1,31 @@
 import { IBook } from "~/types/book";
 import { isArrOfStr } from "~/types/utils";
-import { validateResponse } from "~/api";
+import { validateResponse } from "~/utils/api";
 
-export const getTitleList = () => {
-  const titleListStr = localStorage.getItem("list") || "[]";
-  const titleList: unknown = JSON.parse(titleListStr);
+export const getHashList = () => {
+  const hashListStr = localStorage.getItem("list") || "[]";
+  const hashList: unknown = JSON.parse(hashListStr);
 
-  if (isArrOfStr(titleList)) return titleList;
+  if (isArrOfStr(hashList)) return hashList;
   else {
     localStorage.setItem("list", "[]");
     return [];
   }
 };
 
-export const getBookList = (titleList: string[]) =>
-  titleList
-    .map<unknown>((hash) => JSON.parse(localStorage.getItem(hash) || "{}"))
-    .filter((obj): obj is IBook => {
-      try {
-        return validateResponse(obj);
-      } catch (err) {
-        if (import.meta.env.NODE_ENV === "development")
-          console.log(err.message);
-        return false;
-      }
-    });
+export const getBookHT = (hashList: string[]) => {
+  const bookHT: Record<string, IBook> = {};
 
-export const setBook = (key: string, book: IBook) =>
+  hashList.forEach((hash) => {
+    const obj: unknown = JSON.parse(localStorage.getItem(hash) || "{}");
+    if (validateResponse(obj)) bookHT[hash] = obj;
+  });
+
+  return bookHT;
+};
+
+export const saveBook = (key: string, book: IBook) =>
   localStorage.setItem(key, JSON.stringify(book));
 
-export const updateTitleList = (titleList: string[]) =>
-  localStorage.setItem("list", JSON.stringify(titleList));
+export const updateHashList = (hashList: string[]) =>
+  localStorage.setItem("list", JSON.stringify(hashList));