Hi I am creating an app where a user can search for a book and put it on a shelf depending on which shelf the user clicks on. Currently the user can type a query and many results can get displayed. I want the user to a dropdown on a book and click on a shelf (in the dropdown) to select that book and move it to that shelf.
What I trying to do now is retrieve the book object when the user clicks on a dropdown option (which is a shelf). I want to pass this book object into an api call. How would I retrieve the book object when the user clicks on a specific book's dropdown option? I understand that this might involve event bubbling.
I hope this question makes sense.
SearchPage.js
import React, { useEffect, useState } from 'react';
import { BsArrowLeftShort } from 'react-icons/bs';
import SearchBar from '../components/SearchBar';
import { search, update, getAll } from '../api/BooksAPI';
import Book from '../components/Book';
const SearchPage = () => {
const [query, setQuery] = useState('');
const [data, setData] = useState([]);
const handleChange = (e) => {
setQuery(e.target.value);
};
useEffect(() => {
const bookSearch = setTimeout(() => {
if (query.length > 0) {
search(query).then((res) => {
if (res.length > 0) {
setData(res);
} else setData([]);
});
} else {
setData([]);
}
}, 1000);
return () => clearTimeout(bookSearch);
}, [query]);
const [shelfType, setShelfType] = useState('None');
const [currentBook, setCurrentBook] = useState({});
const handleShelfTypeClick = (e) => {
setShelfType(e.target.value);
console.log(e.target.parentElement.parentElement.parentElement);
//here I want to retrieve the book object when the user clicks on a dropdown option (shelf)
};
return (
<div>
<SearchBar
type="text"
searchValue={query}
placeholder="Search for a book"
icon={<BsArrowLeftShort />}
handleChange={handleChange}
/>
<div className="book-list">
{data !== []
? data.map((book) => (
<Book
handleShelfTypeClick={handleShelfTypeClick}
book={book}
key={book.id}
/>
))
: 'ok'}
</div>
</div>
);
};
export default SearchPage;
Book.js
import React from 'react';
import PropTypes from 'prop-types';
import ButtonDropDown from './ButtonDropDown';
const Book = ({ book, handleShelfTypeClick }) => {
return (
<div className="book">
<img
src={book.imageLinks.thumbnail}
alt={book.title}
className="book-thumbnail"
/>
<ButtonDropDown
choices={['Currently Reading', 'Want to Read', 'Read', 'None']}
getShelfType={handleShelfTypeClick}
/>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
);
};
Book.propTypes = {
handleShelfTypeClick: PropTypes.func.isRequired,
book: PropTypes.shape({
imageLinks: PropTypes.shape({
thumbnail: PropTypes.string.isRequired,
}),
title: PropTypes.string.isRequired,
authors: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
};
export default Book;
ButtonDropDown.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { BsFillCaretDownFill } from 'react-icons/bs';
const ButtonDropDown = ({ choices, label, getShelfType }) => {
const [active, setActive] = useState(false);
const toggleClass = () => {
setActive(!active);
};
return (
<div className="dropdown">
<button
type="button"
className="dropbtn"
onFocus={toggleClass}
onBlur={toggleClass}
>
<BsFillCaretDownFill />
</button>
<div
id="myDropdown"
className={`dropdown-content ${active ? `show` : `hide`}`}
>
<div className="dropdown-label">{label}</div>
{choices.map((choice, index) => (
<button
// eslint-disable-next-line react/no-array-index-key
key={index}
className="dropdown-choice"
onClick={getShelfType}
type="button"
value={choice}
>
{choice}
</button>
))}
</div>
</div>
);
};
ButtonDropDown.propTypes = {
choices: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string,
getShelfType: PropTypes.func.isRequired,
};
ButtonDropDown.defaultProps = {
label: 'Move to...',
};
export default ButtonDropDown;
You are focusing on the signature for the onClick event, but you can actually pass a callback with any format that you need and then build onClick dinamically.
For instance, in Book you could have a callback that receives book and shelf:
const Book = ({ book, doSomethingWithBookAndShelf }) => {
return (
<div className="book">
<img
src={book.imageLinks.thumbnail}
alt={book.title}
className="book-thumbnail"
/>
<ButtonDropDown
choices={['Currently Reading', 'Want to Read', 'Read', 'None']}
onSelectChoice={(choice) => {
// book came from the component props
doSomethingWithBookAndShelf(book, choice);
}}
/>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
);
};
And in ButtonDropDown:
const ButtonDropDown = ({ choices, label, onSelectChoice }) => {
const [active, setActive] = useState(false);
const toggleClass = () => {
setActive(!active);
};
return (
<div className="dropdown">
<button
type="button"
className="dropbtn"
onFocus={toggleClass}
onBlur={toggleClass}
>
<BsFillCaretDownFill />
</button>
<div
id="myDropdown"
className={`dropdown-content ${active ? `show` : `hide`}`}
>
<div className="dropdown-label">{label}</div>
{choices.map((choice, index) => (
<button
// eslint-disable-next-line react/no-array-index-key
key={index}
className="dropdown-choice"
onClick={() => { // we create an specific callback for each item
onSelectChoice(choice);
}}
type="button"
value={choice}
>
{choice}
</button>
))}
</div>
</div>
);
};
Hope that puts you in some direction.
Also, beware it is more React-like to work like that. Avoid using the event objects to get values (i.e. e.target.value).
Related
Whenever I'm clicking on a card to go to the product details page it is automatically going to the bottom of the next page without even scrolling it here is the sample of my code
import React from "react";
import { Link } from "react-router-dom";
import { Rating } from "#material-ui/lab";
const ProductCard = ({ product }) => {
const options = {
value: product.ratings,
readOnly: true,
precision: 0.5,
};
return (
<Link className="productCard" to={`/product/${product._id}`}>
<img src={product.images[0].url} alt={product.name} />
<p>{product.name}</p>
<div>
<Rating {...options} />
<span className="productCardSpan">
{""}({product.numOfReviews} Reviews)
</span>
</div>
<span>{`₹${product.price}/-`}</span>
</Link>
);
};
export default ProductCard;
product details page is starting from here
import React, { useEffect, useState } from "react";
import Carousel from "react-material-ui-carousel";
import "./ProductDetails.css";
import { useSelector, useDispatch } from "react-redux";
import {
clearErrors,
getProductDetails,
newReview,
} from "../../actions/productAction";
import ReviewCard from "./ReviewCard.js";
import Loader from "../layout/Loader/Loader";
import { useAlert } from "react-alert";
import MetaData from "../layout/MetaData";
import { addItemsToCart } from "../../actions/cartAction";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
} from "#material-ui/core";
import { Rating } from "#material-ui/lab";
import { NEW_REVIEW_RESET } from "../../constants/productConstants";
const ProductDetails = ({ match }) => {
const dispatch = useDispatch();
const alert = useAlert();
const { product, loading, error } = useSelector(
(state) => state.productDetails
);
const { success, error: reviewError } = useSelector(
(state) => state.newReview
);
const options = {
size: "large",
value: product.ratings,
readOnly: true,
precision: 0.5,
};
const [quantity, setQuantity] = useState(1);
const [open, setOpen] = useState(false);
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
const increaseQuantity = () => {
if (product.Stock <= quantity) return;
const qty = quantity + 1;
setQuantity(qty);
};
const decreaseQuantity = () => {
if (1 >= quantity) return;
const qty = quantity - 1;
setQuantity(qty);
};
const addToCartHandler = () => {
dispatch(addItemsToCart(match.params.id, quantity));
alert.success("Item Added To Cart");
};
const submitReviewToggle = () => {
open ? setOpen(false) : setOpen(true);
};
const reviewSubmitHandler = () => {
const myForm = new FormData();
myForm.set("rating", rating);
myForm.set("comment", comment);
myForm.set("productId", match.params.id);
dispatch(newReview(myForm));
setOpen(false);
};
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
if (reviewError) {
alert.error(reviewError);
dispatch(clearErrors());
}
if (success) {
alert.success("Review Submitted Successfully");
dispatch({ type: NEW_REVIEW_RESET });
}
dispatch(getProductDetails(match.params.id));
}, [dispatch, match.params.id, error, alert, reviewError, success]);
console.log(product.images);
return (
<>
{loading ? (
<Loader />
) : (
<>
<MetaData title={`${product.name} -- ECOMMERCE`} />
<div className="ProductDetails">
<div>
{product.images &&
product.images.map((item, i) => (
<img
className="CarouselImage"
key={i}
src={item.url}
alt="product"
/>
))}
</div>
<div>
<div className="detailsBlock-1">
<h2>{product.name}</h2>
<p>Product # {product._id}</p>
</div>
<div className="detailsBlock-2">
<Rating {...options} />
<span className="detailsBlock-2-span">
{" "}
({product.numOfReviews} Reviews)
</span>
</div>
<div className="detailsBlock-3">
<h1>{`₹${product.price}`}</h1>
<div className="detailsBlock-3-1">
<div className="detailsBlock-3-1-1">
<button onClick={decreaseQuantity}>-</button>
<input readOnly type="number" value={quantity} />
<button onClick={increaseQuantity}>+</button>
</div>
<button
disabled={product.Stock < 1 ? true : false}
onClick={addToCartHandler}
>
Add to Cart
</button>
</div>
<p>
Status:
<b className={product.Stock < 1 ? "redColor" : "greenColor"}>
{product.Stock < 1 ? "OutOfStock" : "InStock"}
</b>
</p>
</div>
<div className="detailsBlock-4">
Description : <p>{product.description}</p>
</div>
<button onClick={submitReviewToggle} className="submitReview">
Submit Review
</button>
</div>
</div>
<h3 className="reviewsHeading">REVIEWS</h3>
<Dialog
aria-labelledby="simple-dialog-title"
open={open}
onClose={submitReviewToggle}
>
<DialogTitle>Submit Review</DialogTitle>
<DialogContent className="submitDialog">
<Rating
onChange={(e) => setRating(e.target.value)}
value={rating}
size="large"
/>
<textarea
className="submitDialogTextArea"
cols="30"
rows="5"
value={comment}
onChange={(e) => setComment(e.target.value)}
></textarea>
</DialogContent>
<DialogActions>
<Button onClick={submitReviewToggle} color="secondary">
Cancel
</Button>
<Button onClick={reviewSubmitHandler} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
{product.reviews && product.reviews[0] ? (
<div className="reviews">
{product.reviews &&
product.reviews.map((review) => (
<ReviewCard key={review._id} review={review} />
))}
</div>
) : (
<p className="noReviews">No Reviews Yet</p>
)}
</>
)}
</>
);
};
export default ProductDetails;
I tried linking in it but it won't work and someone told me to use useRef but i don't know how to use it
You are using React router dom. In React Router there is the problem that if we redirect to the new route, it won't automatically take you to the top of the page. Such behavior is normal when you navigate between pages.
Since you are using functional components
Try to use the following window scroll to the top when the component mounts.
useEffect(() => {
window.scrollTo(0, 0)
}, [])
Browser scroll your page.
If you don't want to let browser auto scroll your page,
use History.scrollRestoration method
Prevent automatic page location restoration
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
Read more at MDN History.scrollRestoration
-Hello guys , I've encountered a problem which taken a lot time in research but I did not get a solution:
->here I have 2 separate components header.jsx(contains booking.com homepage clone) and hotel.jsx and I want to send a state containing search data from header to hotel using context and use reducer . I've declared a separate context file searchcontext.js ,exported the context and its provider , wrapped the app using the provider in index.js , and I've called the dispatch function in header.js in order to send the needed state , when I call useContext in hotel.js to get the state the console return undefined ,any help please :)
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { SearchContextProvider } from './context/searchcontext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<SearchContextProvider>
<App />
</SearchContextProvider>
</React.StrictMode>
);
searchcontext.js
import { createContext, useReducer } from "react";
const INITIAL_STATE = {
city: undefined,
dates: [],
options: {
adult: undefined,
children: undefined,
room: undefined,
},
};
export const SearchContext = createContext(INITIAL_STATE);
const searchReducer = (action, state) => {
switch (action.type) {
case "NEW_SEARCH":
return action.payload;
case "RESET_SEARCH":
return INITIAL_STATE;
default:
return state;
}
};
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(searchReducer, INITIAL_STATE);
return (
<SearchContext.Provider
value={{
city: state.city,
dates: state.dates,
options: state.options,
dispatch,
}}
>
{children}
</SearchContext.Provider>
);
};
header.jsx file
import "./header.css";
import { DateRange } from "react-date-range";
import { useContext, useState } from "react";
import "react-date-range/dist/styles.css"; // main css file
import "react-date-range/dist/theme/default.css"; // theme css file
import { format } from "date-fns";
import { useNavigate } from "react-router-dom";
import {SearchContext} from "../../context/searchcontext";
const Header = ({ type }) => {
const [destination, setDestination] = useState("");
const [openDate, setOpenDate] = useState(false);
const [dates, setDates] = useState([
{
startDate: new Date(),
endDate: new Date(),
key: "selection",
},
]);
const [openOptions, setOpenOptions] = useState(false);
const [options, setOptions] = useState({
adult: 1,
children: 0,
room: 1,
});
const navigate = useNavigate();
const handleOption = (name, operation) => {
setOptions((prev) => {
return {
...prev,
[name]: operation === "i" ? options[name] + 1 : options[name] - 1,
};
});
};
const{dispatch}= useContext(SearchContext);
const handleSearch = () => {
dispatch({type:"NEW_SEARCH",payload:{destination,dates,options}});
console.log(true)
navigate("/hotels", { state: { destination, dates, options } });
};
return (
<div className="header">
<div className="headerContainer" >
<div className="headerSearch">
<div className="headerSearchItem">
<input
type="text"
placeholder="where are you going?"
className="headerSearchInput"
onChange={(e) => setDestination(e.target.value)}
/>
</div>
<div className="headerSearchItem">
<span
onClick={() => setOpenDate(!openDate)}
className="headerSearchText"
>
{`${format(dates[0].startDate, "dd/MM/yyyy")} to ${format(
dates[0].endDate,
"dd/MM/yyyy"
)}`}
</span>
{openDate && (
<DateRange
editableDateInputs={true}
onChange={(item) => setDates([item.selection])}
moveRangeOnFirstSelection={false}
ranges={dates}
minDate={new Date()}
className="date"
/>
)}
</div>
<div className="headerSearchItem">
<span
onClick={() => setOpenOptions(!openOptions)}
className="headerSearchText">
{`${options.adult}`} adult {`${options.children}`} children
{`${options.room}`} room
</span>
{openOptions && (
<div className="options">
<div className="option">
<span className="optionTitle">Adults</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("adult", "d");
}}
disabled={options.adult <= 1}
>
-
</button>
<span>{options.adult}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("adult", "i");
}}
>
+
</button>
</div>
</div>
<div className="option">
<span className="optionTitle">Children</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("children", "d");
}}
disabled={options.children <= 0}
>
-
</button>
<span>{options.children}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("children", "i");
}}
>
+
</button>
</div>
</div>
<div className="option">
<span className="optionTitle">Rooms</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("room", "d");
}}
disabled={options.room <= 1}
>
-
</button>
<span>{options.room}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("room", "i");
}}
>
+
</button>
</div>
</div>
</div>
)}
</div>
<div className="headerSearchItem">
<button className="headerBtn" onClick={handleSearch}>
Search
</button>
</div>
</div>
</>
)}
</div>
</div>
);
};
export default Header;
hotel.jsx file
import "./hotel.css";
import { useState } from "react";
import { useContext } from "react";
import { SearchContext } from "../../context/searchcontext";
const Hotel = () => {
const [sliderNumber, setSliderNumber] = useState(0);
const [openSlider, setOpenSlider] = useState(false);
//here where the problem encouters
const {state}= useContext(SearchContext);
console.log(state);//returns undefined
return (
<div style={{ margin: -8 }}>
<div className="hotelItemContainer">
<div className="hotelItemWrapper">
<button className="hotelBookBtn"> Reserve or Book Now!</button>
<h1 className="hotelTitle">lorem</h1>
<span className="hotelSpecFeatures">
You're eligible for a Genius discount! , to save at this property
, all you have to do is sign in.
</span>
</div>
</div>
</div>
);
};
export default Hotel;
In your hotel.js you are expecting a state property. However you have not been exposing one in your search context
searchcontext.js
Your context was not exposing a state
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(searchReducer, INITIAL_STATE);
return (
<SearchContext.Provider
value={{
city: state.city,
dates: state.dates,
options: state.options,
state, // you forgot to add this
dispatch,
}}
>
{children}
</SearchContext.Provider>
);
};
alternatively don't use the state property in hotel.js instead use the destructure values that are exposed in your context.
I have a form in a page, when the user inputs the name of a new student and clicks submit, I want the content of that component (the form) to be completely replaced by the submitted name. How can I achieve this (Replace the form with the list onsubmit)?
I have read that I can use conditional rendering to toggle components, but it's not really clear to me how i can apply it here.
StudentListResult.Jsx
import React, { useState } from "react";
import StudentForm from "./StudentForm";
import StudentList from "./StudentList";
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
return (
<div>
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
/>
</div>
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
</div>
);
};
export default StudentListResult;
StudentListForm
import React from "react";
import { v4 as uuidv4 } from "uuid";
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
};
return (
<form onSubmit={addStudent}>
<div>
<input
value={newStudent}
type="text"
placeholder="Student Name"
onChange={(e) => setNewStudent(e.target.value)}
/>
</div>
<div>
<button>Submit</button>
</div>
</form>
);
};
export default StudentListForm;
StudentList.jsx
import React from "react";
const StudentList = ({ students = [], setStudentsList }) => {
return (
<div>
{students.map((student) => (
<ul key={student.id}>
<li>
<p>{student.name}</p>
</li>
</ul>
))}
</div>
);
};
export default StudentList;
So you want to show the form if not submitted and show the list if submitted? You can add a piece of state called submitted and do simple conditional rendering.
const StudentListResult = () => {
const [submitted, setSubmitted] = useState(false)
return (
{submitted ? <StudentList /> : <StudentListForm />}
);
};
And then in your addStudent function, set submitted.
const addStudent = (event) => {
// ...
setSubmitted(true)
}
If you want change form and list visibility state, you need pass custom function to form component:
StudentListResult.jsx:
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
const [getFormSubmitted, setFormSubmitted] = useState(false);
const setCompletedForm = () => {
setFormSubmitted(!getFormSubmitted);
};
return (
<div>
{getFormSubmitted ? (
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
) : (
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
onComplete={setCompletedForm}
/>
</div>
)}
</div>
);
};
Then call this function if form is submitted and all conditions is true
StudentListForm.tsx:
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
onComplete
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
onComplete();
};
I am developing a camera shop application using React js. Here I am facing a problem which is I cannot remove all selected items from the cart.
Note: When a user clicks on the "CHOOSE AGAIN" button then all selected items will be removed from the cart.
Live website link: https://eclectic-wisp-4cf573.netlify.app/.
My code files:
Shop.js file:
import React, { useEffect, useState } from 'react';
import Cart from '../Cart/Cart';
import Product from '../Product/Product';
import './Shop.css';
const Shop = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState([]);
useEffect(() => {
fetch("data.json")
.then((res) => res.json())
.then((data) => setProducts(data));
}, []);
const handleAddToCart = (product) => {
if (cart.length >= 4) {
return;
} else {
const newCart = [...cart, product];
setCart(newCart);
}
};
const choseOeProductForMeHandler = () => {
setCart([cart[Math.floor(Math.random() * cart.length)]]);
};
return (
<div className="shop-container">
<div className="products-container">
{products.map((product) => (
<Product
key={product.id}
product={product}
handleAddToCart={handleAddToCart}
></Product>
))}
</div>
<div className="cart-container">
<Cart
key={cart.id}
cart={cart}
choseOeProductForMeHandler={choseOeProductForMeHandler}
></Cart>
</div>
</div>
);
};
export default Shop;
Cart.js file:
import React from 'react';
import { TrashIcon } from "#heroicons/react/solid";
import './Cart.css';
const Cart = ({ cart, choseOeProductForMeHandler }) => {
return (
<div className="cart">
<h4>Selected Items</h4>
<div className="cart-items">
{cart.map((item) => (
<h4 key={item.id} className="cart-brand-name">
<img className="cart-img" src={item.image} alt="" /> {item.name}
<TrashIcon className="trash-icon"></TrashIcon>
</h4>
))}
</div>
<div>
<button
className="button-1"
onClick={() => choseOeProductForMeHandler()}
>
<p>CHOOSE 1 FOR ME</p>
</button>
<button className="button-2">
<p>CHOOSE AGAIN</p>
</button>
<p>
<small>You can select up to 4 items</small>
</p>
</div>
</div>
);
};
export default Cart;
You need to pass the state setter to Cart.js
In particular pass setCart as prop and attach it the the button as onClick={ () => setCart([])}
Maybe is possible to pass a function from the parent to delete all of them.
In Shop.js
const Shop = () => {
//.......
const resetCart = setProducts([]);
//.......
}
Then you only need to pass it as a prop to the Card component to us it inside that component.
Can you try this?
<Cart
key={cart.id}
cart={cart}
choseOeProductForMeHandler={() => choseOeProductForMeHandler}
/>
Hi I am creating an app where a user can search for a book and put it on a shelf depending on which shelf the user clicks on. Currently the user can type a query and many results can get displayed. The user can open a dropdown on a book and click on a shelf (in the dropdown) to select a shelf for that book.
I want to call a method that will update the shelf of a book. It works only if the shelfType is hardcoded however (shelfTypes are 'wantToRead', 'read', 'currentlyReading'). What I want to happen is that the user clicks on a shelf and that shelf is set as the local state variable shelfType in SearchPage. Then once the shelfType changes, the method to update the shelf of a book will run (it makes an API call to a backend).
But for some strange reason I can only update the shelf if I hardcode the shelf type into the update method, not when I use the value of the state shelfType. What am I doing wrong? I hope this question makes sense.
SearchPage.js
import React, { useEffect, useState } from 'react';
import { BsArrowLeftShort } from 'react-icons/bs';
import SearchBar from '../components/SearchBar';
import { search, update, getAll } from '../api/BooksAPI';
import Book from '../components/Book';
const SearchPage = () => {
const [query, setQuery] = useState('');
const [data, setData] = useState([]);
const handleChange = (e) => {
setQuery(e.target.value);
};
useEffect(() => {
const bookSearch = setTimeout(() => {
if (query.length > 0) {
search(query).then((res) => {
if (res.length > 0) {
setData(res);
} else setData([]);
});
} else {
setData([]); // make sure data is not undefined
}
}, 1000);
return () => clearTimeout(bookSearch);
}, [query]);
const [shelfType, setShelfType] = useState('None');
const [currentBook, setCurrentBook] = useState({});
const doSomethingWithBookAndShelf = (book, shelf) => {
setShelfType(shelf);
setCurrentBook(book);
};
useEffect(() => {
//following line doesn't update like this, but I want it to work like this
update(currentBook, shelfType).then((res) => console.log(res));
// update works if I run update(currentBook, 'wantToRead').then((res) => console.log(res));
getAll().then((res) => console.log(res));
}, [shelfType]);
return (
<div>
<SearchBar
type="text"
searchValue={query}
placeholder="Search for a book"
icon={<BsArrowLeftShort />}
handleChange={handleChange}
/>
<div className="book-list">
{data !== []
? data.map((book) => (
<Book
book={book}
key={book.id}
doSomethingWithBookAndShelf={doSomethingWithBookAndShelf}
/>
))
: 'ok'}
</div>
</div>
);
};
export default SearchPage;
Book.js
import React from 'react';
import PropTypes from 'prop-types';
import ButtonDropDown from './ButtonDropDown';
const Book = ({ book, doSomethingWithBookAndShelf }) => {
return (
<div className="book">
<img
src={book.imageLinks.thumbnail}
alt={book.title}
className="book-thumbnail"
/>
<ButtonDropDown
choices={['Currently Reading', 'Want to Read', 'Read', 'None']}
onSelectChoice={(choice) => {
// book came from the component props
doSomethingWithBookAndShelf(book, choice);
}}
/>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
);
};
Book.propTypes = {
doSomethingWithBookAndShelf: PropTypes.func.isRequired,
book: PropTypes.shape({
imageLinks: PropTypes.shape({
thumbnail: PropTypes.string.isRequired,
}),
title: PropTypes.string.isRequired,
authors: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
};
export default Book;
ButtonDropDown.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { BsFillCaretDownFill } from 'react-icons/bs';
const ButtonDropDown = ({ choices, label, onSelectChoice }) => {
const [active, setActive] = useState(false);
const toggleClass = () => {
setActive(!active);
};
return (
<div className="dropdown">
<button
type="button"
className="dropbtn"
onFocus={toggleClass}
onBlur={toggleClass}
>
<BsFillCaretDownFill />
</button>
<div
id="myDropdown"
className={`dropdown-content ${active ? `show` : `hide`}`}
>
<div className="dropdown-label">{label}</div>
{choices.map((choice, index) => (
<button
// eslint-disable-next-line react/no-array-index-key
key={index}
className="dropdown-choice"
onClick={() => {
// we create an specific callback for each item
onSelectChoice(choice);
}}
type="button"
value={choice}
>
{choice}
</button>
))}
</div>
</div>
);
};
ButtonDropDown.propTypes = {
choices: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string,
onSelectChoice: PropTypes.func.isRequired,
};
ButtonDropDown.defaultProps = {
label: 'Move to...',
};
export default ButtonDropDown;
Cause you're "Want to Read" text in choices is different
choices={['Currently Reading', *'Want to Read'*, 'Read', 'None']}
Based on this // update works if I run update(currentBook, 'wantToRead').then((res) => console.log(res));
"wanToRead" is not equal to "Want to Read"