How to Easily Create a React-Redux Bookstore App Components: Book.js- import PropTypes from 'prop-types'; import { useState } from 'react'; import { useDispatch } from 'react-redux'; import { Progress } from 'antd'; import { removeBook } from '../redux/books/books'; const Book = ({ title, author, id, category, }) => { const dispatch = useDispatch(); const [randomPercentage] = useState(Math.floor(Math.random() * 100)); return ( {category} {title} {author} Comments dispatch(removeBook(id))}>Remove Edit CURRENT CHAPTER Chapter 17 UPDATE PROGRESS ); }; Book.propTypes = { title: PropTypes.string.isRequired, author: PropTypes.string.isRequired, id: PropTypes.string.isRequired, category: PropTypes.string.isRequired, }; export default Book; BookForm.js-import { useState } from 'react'; import { useDispatch } from 'react-redux'; import { addBook } from '../redux/books/books'; const BookForm = () => { const dispatch = useDispatch(); const [title, setTitle] = useState(''); const [author, setAuthor] = useState(''); const [categories] = useState([ 'Fiction', 'Sci-Fi', 'Fantasy', 'Drama', ]); const handleSubmit = (e) => { e.preventDefault(); if (!title || !author) return; const newBook = { author, title, id: new Date().getMilliseconds().toString(), category: categories[Math.floor(Math.random() * categories.length)], }; dispatch(addBook(newBook)); setAuthor(''); setTitle(''); }; return ( ADD NEW BOOK {title} setTitle(e.target.value)} required /> {author} setAuthor(e.target.value)} required /> ADD BOOK ); }; export default BookForm; Navbar.js-import { Link } from 'react-router-dom'; const Navbar = () => (Bookstore CMS {' '} BOOKS CATEGORIES ); export default Navbar; pages: Books.js-import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react- redux'; import Book from '../components/Book'; import BookForm from '../components/BookForm'; import { loadBooks } from '../redux/books/books'; const Books = () => { const books = useSelector((state) => state.books); const dispatch = useDispatch(); useEffect(() => { dispatch(loadBooks); }, []); return ( <>{books && books.map( (book) => ( ), )}
); }; export default Books; Categories-import { useDispatch, useSelector } from 'react-redux'; import { checkStatus } from '../redux/categories/categories'; const Categories = () => { const categories = useSelector((state) => state.categories); const dispatch = useDispatch(); return ( {categories} dispatch(checkStatus())} className=\"add-book-btn\">Check status ); }; export default Categories; redux: Books.js-const ADD_BOOK = 'ADD_BOOK'; const REMOVE_BOOK = 'REMOVE_BOOK'; const BOOKS_LOADED = 'BOOKS_LOADED'; const initialState = []; export default function reducer(state = initialState, action) { switch (action.type) { case BOOKS_LOADED: return action.payload; case ADD_BOOK: return [action.payload, ...state]; case REMOVE_BOOK: return [ ...state.filter((book) => book.id !== action.payload), ]; default: return state; } } export function addBook(book) { return async function addBookAsync(dispatch) { const result = await fetch('https://us-central1-bookstore-api- e63c8.cloudfunctions.net/bookstoreApi/apps/dXhbHW84R2UylfqfKuq7/books', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', }, body: JSON.stringify({ item_id: book.id, category: book.category, title: book.title, author: book.author, }), }); if (result.ok) { dispatch({ type: ADD_BOOK, payload: book }); } }; } export function removeBook(id) { return async function removeBookAsync(dispatch) { const result = await fetch(`https://us-central1-bookstore-api- e63c8.cloudfunctions.net/bookstoreApi/apps/dXhbHW84R2UylfqfKuq7/books/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json;charset=utf-8', }, body: JSON.stringify({ item_id: id, }), }); if (result.ok) { dispatch({ type: REMOVE_BOOK, payload: id }); } }; } export function booksLoaded(books) { return { type: BOOKS_LOADED, payload: books, }; } export async function loadBooks(dispatch) { try { const result = await fetch('https://us-central1-bookstore-api- e63c8.cloudfunctions.net/bookstoreApi/apps/dXhbHW84R2UylfqfKuq7/books'); const data = await result.json(); const books = Object.keys(data).map((key) => ({ id: key, ...data[key][0], })); if (books.length === 0) { throw new Error('There are no books.'); } dispatch(booksLoaded(books)); } catch (error) { dispatch(booksLoaded([])); } } Categories.js-const CHECK_STATUS = 'CHECK_STATUS'; const initialState = []; export default function reducer(state = initialState, action) { switch (action.type) { case CHECK_STATUS: return 'Under construction'; default: return state; } } export function checkStatus() { return { type: CHECK_STATUS, }; } configurestore.js-import { applyMiddleware, combineReducers, createStore, compose, } from 'redux'; import thunk from 'redux-thunk'; import books from './books/books'; import categories from './categories/categories'; const reducers = combineReducers({ books, categories, }); const enhancers = compose( applyMiddleware(thunk), ); const bookstoreStore = createStore( reducers, enhancers, ); export default bookstoreStore; App.css-*, *::before, *::after { box-sizing: inherit; } :root { --bubble-gum-pink: #ff5ab7; -- black: #1e1e1e; --cool-blue: #4386bf; --pinkish-grey: #c7c7c7; --pale-grey: #f5f6fa; --warm- grey: #888; --white: #fff; --very-light-pink: #c4c4c4; --medium-blue: #307bbe; --black-two: #121212; --azure: #0290ff; --white-two: #e8e8e8; --white-three: #fafafa; } body, h1, h2, h3,
h4, h5, h6, p, ol, ul, form { margin: 0; padding: 0; } ol, ul { list-style-type: none; } @font- face { font-family: 'Montserrat'; src: url(./fonts/Montserrat-Regular.ttf); } @font-face { font- family: 'RobotoSlab Bold'; src: url(./fonts/RobotoSlab-Bold.ttf); } @font-face { font-family: 'RobotoSlab Light'; src: url(./fonts/RobotoSlab-Light.ttf); } nav { display: flex; align-items: center; margin: 1.813rem 6.25rem; gap: 2.938rem; } nav ul { display: flex; list-style: none; gap: 2.563rem; } .brand { font-family: 'Montserrat', serif; font-weight: bold; color: var(-- azure); font-size: 1.875rem; } .nav-link { font-family: 'Montserrat', serif; font-weight: normal; font-size: 0.813rem; color: var(--black-two); text-decoration: none; letter-spacing: 1.9px; } .line { border: 1px solid var(--white-two); } .books-line { margin: 0 6.25rem; } .books-section, .form-section, .categories-section { margin: 1.813rem 6.25rem; } .categories- section { display: flex; flex-direction: column; align-items: center; gap: 5em; justify-content: center; } .book { display: flex; border: 1px solid var(--white-two); background-color: var(-- white); padding: 2rem 1.688rem 1.75rem 1.688rem; margin-bottom: 0.938rem; } .book > * { flex-basis: 30%; display: flex; flex-direction: column; align-items: flex-start; } .book > *:first-child { flex-basis: 40%; } .book-category { font-family: 'Montserrat', serif; opacity: 0.5; font-size: 0.875rem; font-weight: bold; color: var(--black-two); } .book-title { font- family: 'RobotoSlab Bold', serif; font-size: 1.375rem; font-weight: bold; color: var(--black- two); } .book-actions { margin-top: 1.313rem; display: flex; } .book-author, .book-actions button { font-family: 'RobotoSlab Light', serif; font-size: 0.875rem; font-weight: 300; color: var(--cool-blue); } .book-actions button { background-color: transparent; border: none; padding: 0; margin: 0; cursor: pointer; } .book-actions button:not(:last-child)::after { content: '|'; margin: 0 0.938rem; line-height: 1.5rem; color: var(--white-two); } .book-progress { display: flex; flex-direction: row; align-items: center; } .progress-stats { display: flex; flex- direction: column; margin-left: 1.313rem; } .percentage { font-family: 'Montserrat', serif; font-size: 2rem; font-weight: normal; color: var(--black-two); } .percentage-completed { opacity: 0.5; font-family: 'Montserrat', serif; font-size: 0.875rem; font-weight: normal; color: var(--black-two); } .current-chapter { opacity: 0.5; font-family: 'RobotoSlab Light', serif; font-size: 0.813rem; font-weight: 300; color: var(--black-two); } .chapter { font-family: 'RobotoSlab Light', serif; font-size: 1rem; font-weight: 300; letter-spacing: -0.4px; color: var(--black-two); } .chapter-info { border-left: solid 1px var(--white-two); padding-left: 4%; margin-left: -4%; } .update-progress { padding: 0.438rem 1.188rem 0.5rem 1.375rem; border-radius: 3px; background-color: var(--azure); font-family: 'RobotoSlab Light', serif; font-size: 0.813rem; font-weight: 300; letter-spacing: 0.5px; text-align: center; color: var(-- white); border: none; margin-top: 1.438rem; width: 11.5rem; } .form { flex-direction: column; } .form form { display: flex; flex-direction: row; } .add-book-title { font-family: 'Montserrat', serif; font-size: 1.25rem; font-weight: bold; letter-spacing: -0.18px; color: var(-- warm-grey); margin-bottom: 1.888rem; } .form input { height: 2.813rem; border-radius: 4px; border: solid 1px var(--white-two); background-color: var(--white); width: 40%; margin- right: 2.125rem; padding: 0 1.063rem; } .form input::placeholder { font-family: 'Montserrat', serif; font-size: 1rem; letter-spacing: -0.15px; color: var(--very-light-pink); } .add-book-btn { width: 11.5rem; height: 2.813rem; border-radius: 3px; border: none; background-color: var(-- azure); font-family: 'RobotoSlab Bold', serif; font-size: 0.813rem; font-weight: bold; letter- spacing: 0.5px; text-align: center; color: var(--white); cursor: pointer; } App.js-import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import './App.css'; import Navbar from './components/Navbar'; import Books from './pages/Books'; import Categories from './pages/Categories'; function App() { return ( } /> } /> ); } export default App;
Search
Read the Text Version
- 1 - 4
Pages: