Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/create and remove books #2

Merged
merged 11 commits into from Jan 18, 2022
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -12,6 +12,9 @@
"react-redux": "^7.2.6",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0",
"redux": "^4.1.2",
"redux-logger": "^3.0.6",
"uuid": "^8.3.2",
"web-vitals": "^2.1.3"
},
"scripts": {
Expand Down
31 changes: 6 additions & 25 deletions src/App.js
@@ -1,44 +1,25 @@
import { Routes, Route } from 'react-router-dom';
import { useSelector } from 'react-redux';
import styles from './App.module.css';

import Nav from './components/Nav/Nav';
import BookList from './components/BookList/BookList';
import CategoryList from './components/CategoryList/CategoryList';

const dummyBooksData = [
{
id: 1,
name: 'Nice book',
category: 'Action',
},
{
id: 2,
name: 'Great book',
category: 'Drama',
},
{
id: 3,
name: 'Best book',
category: 'Sci-Fi',
},
];

function App() {
const App = () => {
const books = useSelector((state) => state.books);
return (
<>
<Nav />
<div className={styles.wrapper}>
<Routes>
<Route path="/" element={<BookList books={dummyBooksData} />} />
<Route path="/" element={<BookList books={books} />} />
<Route path="/categories" element={<CategoryList />} />
<Route
path="/bookstore"
element={<BookList books={dummyBooksData} />}
/>
<Route path="/bookstore" element={<BookList books={books} />} />
</Routes>
</div>
</>
);
}
};

export default App;
93 changes: 74 additions & 19 deletions src/components/AddBook/AddBook.js
@@ -1,23 +1,78 @@
import React from 'react';
/* eslint-disable operator-linebreak */
import { useDispatch } from 'react-redux';
import { useState } from 'react';
import { v4 as uuid } from 'uuid';
import styles from './AddBook.module.css';

const AddBook = () => (
<form className={styles['add-form']}>
<input type="text" placeholder="Add a book" />
<label htmlFor="categories">
Choose a category:
<select id="categories">
<option value="none" disabled hidden selected>
Categories
</option>
<option value="action">Action</option>
<option value="drama">Drama</option>
<option value="sci-fi">Sci-Fi</option>
<option value="horror">Horror</option>
</select>
</label>
<button type="submit">Add</button>
</form>
);
import { addBook } from '../../redux/books/books';

const AddBook = () => {
const [bookTitle, setBookTitle] = useState('');
const [bookAuthor, setBookAuthor] = useState('');
const [categoryValue, setCategoryValue] = useState('none');
const dispatch = useDispatch();

const BookTitleInputHandler = (e) => {
setBookTitle(e.target.value);
};

const BookAuthorInputHandler = (e) => {
setBookAuthor(e.target.value);
};

const submitBookHandler = (e) => {
e.preventDefault();
const newBook = {
id: uuid(),
title: bookTitle.trim(),
author: bookAuthor.trim(),
};
dispatch(addBook(newBook));
};

return (
<form className={styles['add-form']}>
<input
type="text"
placeholder="Add a book title"
value={bookTitle}
onChange={BookTitleInputHandler}
/>
<input
type="text"
placeholder="Book author"
value={bookAuthor}
onChange={BookAuthorInputHandler}
/>
<label htmlFor="categories">
Choose a category:
<select
id="categories"
value={categoryValue}
onChange={(e) => setCategoryValue(e.target.value)}
>
<option value="none" disabled hidden>
Categories
</option>
<option value="action">Action</option>
<option value="drama">Drama</option>
<option value="sci-fi">Sci-Fi</option>
<option value="horror">Horror</option>
</select>
</label>
<button
type="submit"
onClick={submitBookHandler}
disabled={
bookTitle.trim() === '' ||
bookAuthor.trim() === '' ||
categoryValue === 'none'
}
>
Add
</button>
</form>
);
};

export default AddBook;
34 changes: 25 additions & 9 deletions src/components/Book/Book.js
@@ -1,18 +1,34 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import styles from './Book.module.css';

const Book = ({ book }) => (
<>
<h4 className={styles['book-name']}>{book.name}</h4>
<button type="button">Remove</button>
</>
);
import { removeBook } from '../../redux/books/books';

const Book = ({ book }) => {
const dispatch = useDispatch();

const removeBookHandler = () => {
dispatch(removeBook(book.id));
};

return (
<>
<h4>
<span className={styles['book-title']}>{book.title}</span>
<span className={styles['book-author']}>{` by '${book.author}'`}</span>
</h4>
<button type="button" onClick={removeBookHandler}>
Remove
</button>
</>
);
};

Book.propTypes = {
book: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
author: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
}).isRequired,
};
Expand Down
8 changes: 7 additions & 1 deletion src/components/Book/Book.module.css
@@ -1,3 +1,9 @@
.book-name {
.book-title {
font-weight: bold;
font-size: 2em;
}

.book-author {
font-weight: bold;
color: gray;
}
3 changes: 3 additions & 0 deletions src/components/BookList/BookList.js
Expand Up @@ -8,6 +8,9 @@ import Book from '../Book/Book';
const BookList = ({ books }) => (
<div className={styles['book-list__wrapper']}>
<AddBook />
{books.length === 0 && (
<div className={styles['no-books-div']}>No books added yet</div>
)}
<ul className={styles['book-list']}>
{books.map((book) => (
<li key={book.id}>
Expand Down
7 changes: 7 additions & 0 deletions src/components/BookList/BookList.module.css
Expand Up @@ -13,6 +13,13 @@
align-items: center;
}

.no-books-div {
font-size: 3em;
font-weight: bold;
color: #8d8d8d;
user-select: none;
}

.book-list li {
display: flex;
justify-content: space-between;
Expand Down
10 changes: 7 additions & 3 deletions src/index.js
@@ -1,15 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import './index.css';

import App from './App';
import store from './redux/configureStore';

ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById('root'),
);
27 changes: 27 additions & 0 deletions src/redux/books/books.js
@@ -0,0 +1,27 @@
const ADD_BOOK = 'bookStore/books/ADD_BOOK';
const REMOVE_BOOK = 'bookStore/books/REMOVE_BOOK';

const initialState = [];

export const addBook = (payload) => ({
type: ADD_BOOK,
payload,
});

export const removeBook = (payload) => ({
type: REMOVE_BOOK,
payload,
});

const reducer = (state = initialState, action) => {
switch (action.type) {
case ADD_BOOK:
return [...state, action.payload];
case REMOVE_BOOK:
return state.filter((book) => book.id !== action.payload);
default:
return state;
}
};

export default reducer;
11 changes: 11 additions & 0 deletions src/redux/configureStore.js
@@ -0,0 +1,11 @@
import { createStore, combineReducers, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import books from './books/books';

const reducer = combineReducers({
books,
});

const store = createStore(reducer, applyMiddleware(logger));

export default store;