From 917b00ea18379bb30296663cf63ab582a038fae4 Mon Sep 17 00:00:00 2001 From: dm1sh Date: Fri, 16 Jul 2021 03:59:33 +0500 Subject: [PATCH] Added global book list context and useLibrary hook for it --- src/App/index.tsx | 13 ++++--- src/api.ts | 13 +++---- src/context.tsx | 18 +++++++++ src/hooks/useLibrary.ts | 38 ++++++++++++++++++ src/pages/Bookshelf/AddBook/index.tsx | 1 - src/pages/Bookshelf/index.tsx | 11 ++---- src/pages/UploadForm/index.tsx | 8 ++-- src/utils/localStorage.ts | 55 ++++++++++++--------------- 8 files changed, 102 insertions(+), 55 deletions(-) create mode 100644 src/context.tsx create mode 100644 src/hooks/useLibrary.ts diff --git a/src/App/index.tsx b/src/App/index.tsx index 1218a8e..a333545 100644 --- a/src/App/index.tsx +++ b/src/App/index.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Route, Switch } from "wouter"; +import { BookListContextProvider } from "~/context"; import { Bookshelf } from "~/pages/Bookshelf"; import { BookView } from "~/pages/BookView"; @@ -9,11 +10,13 @@ import styles from "./App.module.css"; export const App = () => (
- - - - - + + + + + + +
); diff --git a/src/api.ts b/src/api.ts index 6bb44e8..37e03c0 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,4 @@ -import { IBook, optionalBookProps, requiredBookProps } from "~/types/book"; +import { IBook, requiredBookProps } from "~/types/book"; import { API_URL } from "./constants"; @@ -39,12 +39,11 @@ export const submitFile = async ( } }; -export const validateResponse = ( - content: Record -): content is IBook => { - for (const key of requiredBookProps) - if (!(key in content)) - throw new Error(`${key} is not specified in server response`); +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`); return true; }; diff --git a/src/context.tsx b/src/context.tsx new file mode 100644 index 0000000..24ff9af --- /dev/null +++ b/src/context.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { useLibrary, UseLibraryReturnTuple } from "./hooks/useLibrary"; +import { IBook } from "./types/book"; + +export const BookListContext = React.createContext([ + [], + (book: IBook) => {}, +]); + +export const BookListContextProvider: React.FC = ({ children }) => { + const library = useLibrary(); + + return ( + + {children} + + ); +}; diff --git a/src/hooks/useLibrary.ts b/src/hooks/useLibrary.ts new file mode 100644 index 0000000..a0a8d34 --- /dev/null +++ b/src/hooks/useLibrary.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { IBook } from "~/types/book"; +import { + getBookList, + getTitleList, + setBook, + updateTitleList, +} from "~/utils/localStorage"; + +export type AddBookFT = (book: IBook) => void; + +export type UseLibraryReturnTuple = [IBook[], AddBookFT]; + +export const useLibrary = (): UseLibraryReturnTuple => { + const [bookList, setBookList] = useState([]); + const [titleList, setTitleList] = useState([]); + + const addBook: AddBookFT = (book) => { + const key = book.hash || Date.now().toString(); + if (key && !titleList.includes(key)) { + setTitleList([key, ...titleList]); + setBookList([book, ...bookList]); + + setBook(key, book); + } + }; + + useEffect(() => { + const receivedTitleList = getTitleList(); + + setTitleList(receivedTitleList); + setBookList(getBookList(receivedTitleList)); + }, []); + + useEffect(() => updateTitleList(titleList), [titleList]); + + return [bookList, addBook]; +}; diff --git a/src/pages/Bookshelf/AddBook/index.tsx b/src/pages/Bookshelf/AddBook/index.tsx index dd4551f..84828f2 100644 --- a/src/pages/Bookshelf/AddBook/index.tsx +++ b/src/pages/Bookshelf/AddBook/index.tsx @@ -3,7 +3,6 @@ import { useLocation } from "wouter"; import plusIcon from "~/assets/plus.svg"; import styles from "./AddBook.module.css"; -import { BASE_URL } from "~/constants"; export const AddBook = () => { const [_, setLocation] = useLocation(); diff --git a/src/pages/Bookshelf/index.tsx b/src/pages/Bookshelf/index.tsx index 9851c6d..697fe4f 100644 --- a/src/pages/Bookshelf/index.tsx +++ b/src/pages/Bookshelf/index.tsx @@ -1,18 +1,13 @@ -import React, { useEffect, useState } from "react"; -import { IBook } from "~/types/book"; +import React, { useContext, useEffect, useState } from "react"; import styles from "./Bookshelf.module.css"; import { BookItem } from "./BookItem"; import { AddBook } from "./AddBook"; -import { readBooks } from "~/utils/localStorage"; +import { BookListContext } from "~/context"; export const Bookshelf = () => { - const [books, setBooks] = useState([]); - - useEffect(() => { - setBooks(readBooks()); - }, []); + const [books] = useContext(BookListContext); return (
diff --git a/src/pages/UploadForm/index.tsx b/src/pages/UploadForm/index.tsx index 38df2f6..7c5f5fc 100644 --- a/src/pages/UploadForm/index.tsx +++ b/src/pages/UploadForm/index.tsx @@ -1,16 +1,18 @@ -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useLocation } from "wouter"; import plusIcon from "~/assets/plus.svg"; import styles from "./UploadForm.module.css"; import { submitFile, validateResponse, validState } from "~/api"; -import { saveBook } from "~/utils/localStorage"; +import { BookListContext } from "~/context"; export const UploadForm = () => { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const [_, setLocation] = useLocation(); + const [__, saveBook] = useContext(BookListContext); + const processFile = async (file: File | undefined) => { try { if (validState(file)) { @@ -23,7 +25,7 @@ export const UploadForm = () => { console.log(validateResponse(res)); if (validateResponse(res)) { - saveBook(res, res.hash || Date.now().toString()); + saveBook(res); setLocation("/"); } } diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts index ca0d3c1..d454a84 100644 --- a/src/utils/localStorage.ts +++ b/src/utils/localStorage.ts @@ -1,40 +1,33 @@ import { IBook } from "~/types/book"; import { isArrOfStr } from "~/types/utils"; import { validateResponse } from "~/api"; -import { BookItem } from "~/pages/Bookshelf/BookItem"; -const readBookList = ( - cb: (bookList: string[]) => T, - defaultValue: T extends void ? undefined : T -) => { - const bookListStr = localStorage.getItem("list") || "[]"; - const bookList: unknown = JSON.parse(bookListStr); +export const getTitleList = () => { + const titleListStr = localStorage.getItem("list") || "[]"; + const titleList: unknown = JSON.parse(titleListStr); - if (isArrOfStr(bookList)) return cb(bookList); - return defaultValue; + if (isArrOfStr(titleList)) return titleList; + else { + localStorage.setItem("list", "[]"); + return []; + } }; -export const saveBook = (bookObj: IBook, key: string) => - readBookList((bookList) => { - if (!bookList.includes(key)) { - const newBookList = [key, ...bookList]; +export const getBookList = (titleList: string[]) => + titleList + .map((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; + } + }); - localStorage.setItem("list", JSON.stringify(newBookList)); - localStorage.setItem(key, JSON.stringify(bookObj)); - } - }, undefined); +export const setBook = (key: string, book: IBook) => + localStorage.setItem(key, JSON.stringify(book)); -export const readBooks = (): IBook[] => - readBookList( - (bookList) => - bookList - .map((hash) => JSON.parse(localStorage.getItem(hash) || "{}")) - .filter((e) => { - try { - return validateResponse(e); - } catch (err) { - return false; - } - }), - [] - ); +export const updateTitleList = (titleList: string[]) => + localStorage.setItem("list", JSON.stringify(titleList));