diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 2923eaa..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,3 +0,0 @@ -# How to contribute - -The files in this repository are used in the course videos and are the starting point for all students. Because we want all students to have the same experience going through course, if your pull request alters any of the core files, then it (most likely) will _not_ be merged into the project. diff --git a/package.json b/package.json index 1ba84cc..e2c540f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "lodash.debounce": "^4.0.8", "prop-types": "^15.6.2", "react": "^17.0.1", "react-dom": "^17.0.1", diff --git a/src/App.css b/src/App.css index 049366c..b780245 100644 --- a/src/App.css +++ b/src/App.css @@ -78,10 +78,7 @@ body, /* search page */ .search-books-bar { - position: fixed; width: 100%; - top: 0; - left: 0; z-index: 5; display: flex; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 0 6px rgba(0, 0, 0, 0.23); diff --git a/src/App.js b/src/App.js index bc670e9..5dd9b60 100644 --- a/src/App.js +++ b/src/App.js @@ -1,32 +1,71 @@ +import { useState, useEffect } from 'react' import './App.css' import Navigation from './components/Navigation/Navigation' import Main from './components/Main/Main' -import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom' +import { BooksProvider } from './BooksProvider' +import { getAll, update } from './BooksAPI' const BooksApp = () => { - // state = { - // /** - // * TODO: Instead of using this state variable to keep track of which page - // * we're on, use the URL in the browser's address bar. This will ensure that - // * users can use the browser's back and forward buttons to navigate between - // * pages, as well as provide a good URL they can bookmark and share. - // */ - // showSearchPage: false, - // } + const [searchResults, setSearchResults] = useState([]) + const [books, setBooks] = useState([]) + const [shouldUpdate, setShouldUpdate] = useState(true) + + const currentlyReading = books && books.filter((book) => book.shelf === 'currentlyReading') + const wantToRead = books && books.filter((book) => book.shelf === 'wantToRead') + const read = books && books.filter((book) => book.shelf === 'read') + + useEffect(() => { + if (shouldUpdate) { + getAll() + .then((data) => { + setBooks(data) + }) + .then(() => setShouldUpdate(false)) + .catch((err) => console.log(err)) + } + setShouldUpdate(false) + }, [shouldUpdate]) + + const handleShelf = (shelf, book) => { + const { id } = book + + // update server + update(id, shelf) + // check if book is already on the shelf + const isBook = books.find((book) => book.id === id) + + // when book already exists, change it to the selected shelf + if (isBook !== undefined) { + const myBooks = [...books] + const bookIndex = myBooks.findIndex((book) => book.id === id) + myBooks[bookIndex].shelf = shelf + setBooks(myBooks) + + // When book does not exists, add it to the selected shelf and update the state + } else { + // force update to update the books + setShouldUpdate(true) + } + } return (
- - -
-
-
- -
-
-
-
+ + {/* Pass books down to make it available for any consumer component within the provider */} + +
+
) } diff --git a/src/BooksProvider.js b/src/BooksProvider.js new file mode 100644 index 0000000..f2eab6b --- /dev/null +++ b/src/BooksProvider.js @@ -0,0 +1,7 @@ +import { createContext } from 'react' + +export const BooksContext = createContext([]) + +export const BooksProvider = BooksContext.Provider + +export default BooksContext diff --git a/src/assets/book-placeholder.jpeg b/src/assets/book-placeholder.jpeg new file mode 100644 index 0000000..e4ba8c7 Binary files /dev/null and b/src/assets/book-placeholder.jpeg differ diff --git a/src/components/Book/Book.js b/src/components/Book/Book.js index c569e6d..171c396 100644 --- a/src/components/Book/Book.js +++ b/src/components/Book/Book.js @@ -1,13 +1,21 @@ +import { useContext } from 'react' +import BooksProvider from '../../BooksProvider' import BookShelfChanger from '../BookShelfChanger/BookShelfChanger' +import placeholder from '../../assets/book-placeholder.jpeg' -const Book = ({ book, handleShelf }) => { - const { - imageLinks: { thumbnail }, - title, - authors, - shelf, - id, - } = book +const Book = ({ book, search }) => { + const { title, authors, shelf, id, imageLinks } = book + + const { books } = useContext(BooksProvider) + + // if book list coming from search results, check if book is already is in any shelf mybooks + const isBook = () => { + if (search) { + return books && books.find((book) => book.id === id) + } + } + + const thumbnail = imageLinks ? book.imageLinks.thumbnail : placeholder const bookAuthors = authors && authors.map((author) =>

{author}

) @@ -23,7 +31,7 @@ const Book = ({ book, handleShelf }) => { backgroundImage: `url(${thumbnail})`, }} > - +
{title}
diff --git a/src/components/BookList/BookList.js b/src/components/BookList/BookList.js new file mode 100644 index 0000000..8a43704 --- /dev/null +++ b/src/components/BookList/BookList.js @@ -0,0 +1,9 @@ +import Book from '../Book/Book' + +const BookList = ({ books, search }) => { + return ( +
    {books && books.map((book) => )}
+ ) +} + +export default BookList diff --git a/src/components/BookShelf/BookShelf.js b/src/components/BookShelf/BookShelf.js index 06c99c4..dcc8b49 100644 --- a/src/components/BookShelf/BookShelf.js +++ b/src/components/BookShelf/BookShelf.js @@ -1,13 +1,11 @@ -import Book from '../Book/Book' +import BookList from '../BookList/BookList' -const BookShelf = ({ name, books, handleShelf }) => { +const BookShelf = ({ name, books }) => { return (
-

{name}

+

{name && name}

-
    - {books && books.map((book) => )} -
+
) diff --git a/src/components/BookShelfChanger/BookShelfChanger.js b/src/components/BookShelfChanger/BookShelfChanger.js index b5a50c7..13e24b6 100644 --- a/src/components/BookShelfChanger/BookShelfChanger.js +++ b/src/components/BookShelfChanger/BookShelfChanger.js @@ -1,7 +1,19 @@ -const BookShelfChanger = ({ shelf, handleShelf, id }) => { +import { useContext, useState } from 'react' +import BooksContext from '../../BooksProvider' + +const BookShelfChanger = ({ book }) => { + const bookShelf = book.shelf ? book.shelf : 'none' + const { handleShelf } = useContext(BooksContext) + const [value, setValue] = useState(bookShelf) + + const handleChange = (e) => { + handleShelf(e.target.value, book) + setValue(e.target.value) + } + return (
- diff --git a/src/components/Main/Main.js b/src/components/Main/Main.js index a24c366..d6c688e 100644 --- a/src/components/Main/Main.js +++ b/src/components/Main/Main.js @@ -1,33 +1,34 @@ -import { useState, useEffect } from 'react' -import { getAll, update } from '../../BooksAPI' +import { useContext } from 'react' import BookShelf from '../BookShelf/BookShelf' +import BooksContext from '../../BooksProvider' +import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom' +import Search from '../Search/Search' const Main = () => { - const [books, setBooks] = useState([]) - const currentlyReading = books && books.filter((book) => book.shelf === 'currentlyReading') - const wantToRead = books && books.filter((book) => book.shelf === 'wantToRead') - const readBooks = books && books.filter((book) => book.shelf === 'read') - - const handleShelf = (shelf, id) => { - const myBooks = [...books] - const bookIndex = myBooks.findIndex((book) => book.id === id) - myBooks[bookIndex].shelf = shelf - setBooks(myBooks) - update(id, shelf) - } - - useEffect(() => { - getAll().then((data) => setBooks(data)) - }, []) + const { currentlyReading, wantToRead, read } = useContext(BooksContext) return ( -
-
- - - -
-
+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
) } diff --git a/src/components/Search/search.js b/src/components/Search/search.js index 386c519..ac23d16 100644 --- a/src/components/Search/search.js +++ b/src/components/Search/search.js @@ -1,25 +1,65 @@ +import { useState, useEffect } from 'react' +import { Link } from 'react-router-dom' +import BookList from '../BookList/BookList' +import { search } from '../../BooksAPI' +import { useDebounce } from '../../hooks/useDebounce' + const Search = () => { + // APi query value (delayed) + const [query, setQuery] = useState('') + const [searchResults, setSearchResults] = useState([]) + const [message, setMessage] = useState('') + // Inpute value + const [value, setValue] = useState('') + + // fetch api + useEffect(() => { + if (!query) { + setSearchResults([]) + setMessage('') + } + query && search(query).then((data) => setSearchResults(data)) + }, [query]) + + // display message + useEffect(() => { + if (query.length && !searchResults.length) { + setTimeout(() => { + setMessage('Not found! Try another search term!') + }, 800) + } + }, [query, searchResults]) + + const debouncedSave = useDebounce((value) => setQuery(value), 500) + + const handleChange = (e) => { + const text = e.target.value.toLowerCase() + // input value to display immediately + setValue(text) + // delay query to deminish api calls + debouncedSave(text) + } + return (
- +
- {/* - NOTES: The search from BooksAPI is limited to a particular set of search terms. - You can find these search terms here: - https://github.com/udacity/reactnd-project-myreads-starter/blob/master/SEARCH_TERMS.md - - However, remember that the BooksAPI.search method DOES search by title or author. So, don't worry if - you don't find a specific author or title. Every search is limited by search terms. - */} - +
-
    + {searchResults.length ? : {message}}
    ) } + +export default Search diff --git a/src/hooks/useDebounce.js b/src/hooks/useDebounce.js new file mode 100644 index 0000000..8794959 --- /dev/null +++ b/src/hooks/useDebounce.js @@ -0,0 +1,8 @@ +import _debounce from 'lodash.debounce' +import { useCallback } from 'react' + +export const useDebounce = (callback, delay) => { + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedCallback = useCallback(_debounce(callback, delay), [delay]) + return debouncedCallback +} diff --git a/yarn.lock b/yarn.lock index 4132a6f..95d27f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6786,6 +6786,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"