i'm trying to make product detail page but i am struggle to solve this problem. I fetch products from sanity.io and return the data using map. i want whenever i click the detail btn it's direct me to detail page and get the information of product
here is my code
const Products = () => {
const [allProducts, setAllProducts] = useState([]);
useEffect(() => {
async function getAllProducts() {
const allProduct = '*[_type == "product"]';
const response = await client.fetch(allProduct);
setAllProducts(await response);
}
getAllProducts();
}, []);
return (
<>
<Navbar />
<div className="container-fluid ">
<div className="row g-3 mb-5 ">
{allProducts.map((product) => {
console.log(product);
return (
<div className="col-12 col-md-3" key={product._id}>
<div className="card">
<img
src={urlFor(product.image[0])}
className="card-img-top"
alt={product.name}
/>
<div className="card-body">
<h1>${product.price}</h1>
<h5 className="card-title">{product.name} </h5>
<h6>{product.detail}</h6>
<div>
<Link to="/detail" className="btn btn-danger m-2">
Detail
</Link>
</div>
</div>
</div>
</div>
);
})}
</div>
<Footer />
</div>
</>
);
};
I have an Index.js where I am getting data via async function and sending data to ProductList Component and from ProductList Component I am sending an id onclick of product Image and then I get Product Detail Page a Single Product. I get Single Product with no issues but when I refresh then I get an error in console saying - Cannot read properties of undefined (reading 'filter') I have used REDUX.
INDEX.JS
import React, { useState, useEffect } from "react";
// import PageNotFound from "../other/page-not-found/PageNotFound";
import ProductList from "./product-list/ProductList";
import ProductFilter from "./product-filter/ProductFilter";
import ProductSearch from "./product-search/ProductSearch";
import Cart from "./cart/Cart";
// import MobileFilterModal from "./mobile-filter-modal/MobileFilterModal";
import { useDispatch, useSelector } from "react-redux";
import { setProductListData } from "../../redux/actions/product-list-actions/ProductListActions";
import Loading from "../other/loading/Loading";
import Skeleton from "../other/skeletons/Skeleton";
// import PageNotFound from "../other/page-not-found/PageNotFound";
const Index = () => {
const [isLoading, setIsLoading] = useState(true);
const dispatch = useDispatch();
const filters = useSelector(
(state) => state.productList.productListData.filters
);
const products = useSelector(
(state) => state.productList.productListData.products
);
// console.log(products);
useEffect(() => {
getProductsData();
}, []);
const getProductsData = async () => {
try {
const response = await fetch("http://localhost:8000/productListing");
const data = await response.json();
if (data) {
setIsLoading(false);
dispatch(setProductListData(data));
} else {
setIsLoading(true);
}
} catch (error) {
console.log(error);
}
};
// if (isLoading) {
// return <Loading loadingProductList={"products"} />;
// }
return (
<>
{filters && products && (
<main>
<div className="dvMain">
<div className="container-fluid">
<div className="row">
<div className="dvFilters col-lg-3 col-xl-2 d-none d-lg-block text-right">
<div className="sticky-top" style={{ top: "90px" }}>
{isLoading
? filters.map((filter) => {
return (
<Skeleton key={filter.id} filterId={filter.id} />
);
})
: filters &&
filters.map((filter) => {
return <ProductFilter key={filter.id} {...filter} />;
})}
</div>
</div>
<div className="dvProducts col-lg-6 col-xl-8">
<div className="row">
<div className="dvSearch col-sm-9 col-md-10 col-lg-12 mb-3">
{isLoading ? (
<Skeleton productSearch={"productSearch"} />
) : (
<ProductSearch />
)}
</div>
<div className="dvFilterMobile col-sm-3 col-md-2 d-lg-none text-center text-sm-right mb-3">
<a
href=""
className="btn bg-light w-100"
data-toggle="modal"
data-target="#mobileFiltersModal"
>
<i className="fa fa-filter"></i>Filter
</a>
</div>
</div>
<div className="row">
{isLoading
? products.map((product) => {
return (
<Skeleton key={product.id} productId={product.id} />
);
})
: products &&
products.map((product) => {
return <ProductList key={product.id} {...product} />;
})}
</div>
</div>
{isLoading ? <Skeleton cart={"cart"} /> : <Cart />}
</div>
</div>
</div>
</main>
)}
</>
);
};
export default Index;
PRODUCTLIST.JS
import React from "react";
import { Link } from "react-router-dom";
import "./productlist.css";
const ProductList = ({ id, img, pack, price, size, heading, description }) => {
// console.log(id, img, pack, price, size, heading, description);
// console.log(id, img, pack, price, size, heading, description);
return (
<>
<div className="col-6 col-md-4 col-lg-6 col-xl-3 mb-4">
<div className="border border-light shadow-sm p-1 h-100">
<div className="image">
<p className="packs">pack of 5</p>
<div className="bg-light text-center pt-2 pb-2 mb-1">
<Link className="d-inline-block" to={`/${id}`}>
<img src={img} className="img-fluid" alt={heading} />
</Link>
</div>
<h5 className="heading-5 text-center">{heading}</h5>
</div>
<div className="description d-flex justify-content-between mb-1">
<div className="paragraph">
<p>{size}</p>
</div>
<div className="paragraph mr-2">
<span>
<i className="fa fa-inr"></i>
<span>{price}</span>
</span>
</div>
</div>
<div className="addBtn text-center">
<button className="btn btn-white w-100" href="detail.html">
Add to Bag
</button>
</div>
</div>
</div>
</>
);
};
export default ProductList;
PRODUCTDETAIL.JS
import React, { useState, useEffect } from "react";
import Cart from "../cart/Cart";
import "./productdetail.css";
import Loading from "../../other/loading/Loading";
import PageNotFound from "../../other/page-not-found/PageNotFound";
import { Link, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { setSelectedProduct } from "../../../redux/actions/product-detail-actions/ProductDetailActions";
const ProductDetail = () => {
const [isLoading, setIsLoading] = useState(true);
const { id } = useParams();
const dispatch = useDispatch();
const products = useSelector(
(state) => state.productList.productListData.products
);
const singleProduct = useSelector(
(state) => state.singleProduct.singleProduct
);
// console.log(id);
// console.log(products);
// console.log(singleProduct);
useEffect(() => {
getProductId();
}, []);
const getProductId = () => {
window.scrollTo(0, 0);
// localStorage.setItem("setProducts", JSON.stringify(products));
// let getProducts = JSON.parse(localStorage.getItem("setProducts"));
products ? setIsLoading(false) : setIsLoading(true);
let singleProduct = [];
singleProduct = products.filter((product) => {
return product.id === parseInt(id);
});
// console.log(singleProduct);
dispatch(setSelectedProduct(singleProduct));
};
if (isLoading) {
return <Loading loadingProductDetail={"product detail"} />;
}
return (
<>
{singleProduct && (
<main>
<div className="dvMain">
<div className="container">
<div className="row">
{singleProduct ? (
singleProduct.map((product) => {
const {
id,
img,
heading,
description,
category,
price,
size,
} = product;
return (
<div key={id} className="col-lg-9">
<div className="row">
<div className="dvProducts col-12">
<div className="row">
<div className="dvBack col-12">
<Link
to="/products"
className="mb-1 d-inline-block"
>
<i className="fa fa-angle-left"></i>
<span> Back</span>
</Link>
</div>
<div className="col-12">
<div className="row">
<div className="col-12 col-md-6 col-xl-4 mb-3">
<div className="border border-light shadow-sm p-1 h-100 d-flex justify-content-center align-items-center">
<div className="bg-light text-center h-100">
<a className="d-inline-block h-100">
<img
src={img}
className="img-fluid"
alt={heading}
/>
</a>
</div>
</div>
</div>
<div className="col-12 col-md-6 col-xl-8 d-flex mb-3 mb-xl-0">
<div className="m-md-auto">
<div>
<h2 className="heading-2">{heading}</h2>
</div>
<div className="mb-2">
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
</div>
<div className="mb-3">
<p className="paragraph">
{description}
</p>
</div>
<div className="d-flex mb-3">
<div className="mr-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Size:
</h5>
<span className="paragraph d-inline-block">
{size}
</span>
</div>
<div className="mr-2 ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Category:
</h5>
<span className="paragraph d-inline-block">
{category}
</span>
</div>
<div className="ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Price:
</h5>
<span className="paragraph d-inline-block">
<i className="fa fa-inr"></i>
<span className="d-inline-block">
{price}.00
</span>
</span>
</div>
</div>
<div>
<button
className="btn btn-black"
href="/-"
>
Add to Bag
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="dvTabs col-12 mt-4">
<div className="row">
<div className="col-12">
<div className="row">
<div className="col-12">
<ul
className="nav nav-tabs"
id="myTab"
role="tablist"
>
<li className="nav-item">
<a
className="heading-5 nav-link active"
id="description-tab"
data-toggle="tab"
href="#description"
role="tab"
aria-controls="description"
aria-selected="true"
>
Description
</a>
</li>
</ul>
</div>
</div>
<div
className="dvTabsContent tab-content row"
id="myTabContent"
>
<div
className="tab-pane fade show active py-3 col-12"
id="description"
role="tabpanel"
aria-labelledby="description-tab"
>
<div className="row">
<div className="col-12">
<div className="table-responsive">
<table className="table table-striped table-bordered">
<thead>
<tr>
<th
className="heading-5"
scope="col"
>
Details
</th>
<th
className="heading-5"
scope="col"
>
Weight
</th>
<th
className="heading-5"
scope="col"
>
1Glass 250ml
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Amount per serving</td>
<td>250ml</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
})
) : (
<PageNotFound />
)}
<Cart id={id} />
</div>
</div>
</div>
</main>
)}
</>
);
};
export default ProductDetail;
APP.JS
import React from "react";
import "./App.css";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Homepage from "./containers/home-page/index";
import Header from "./containers/header/Header";
import Footer from "./containers/footer/Footer";
// import ProductListing from "./containers/products/product-list/ProductList";
import ProductDetail from "./containers/products/product-detail/ProductDetail";
import PageNotFound from "./containers/other/page-not-found/PageNotFound";
import Account from "./containers/account/index";
import Checkout from "./containers/checkout/Index";
import LoginModal from "./containers/auth/modal/login-modal/LoginModal";
import SignupModal from "./containers/auth/modal/signup-modal/SignupModal";
import MobileCartModal from "./containers/products/mobile-cart-modal/MobileCartModal";
import Index from "./containers/products";
const App = () => {
return (
<>
<BrowserRouter>
<Header />
<main>
<Switch>
<Route path="/" exact component={Homepage} />
<Route path="/products" component={Index} />
<Route path="/:id" children={<ProductDetail />} />
<Route path="/checkout" component={Checkout} />
<Route path="/account" component={Account} />
<Route path="*" component={PageNotFound} />
</Switch>
</main>
<Footer />
<LoginModal />
<SignupModal />
<MobileCartModal />
</BrowserRouter>
</>
);
};
export default App;
PRODUCTDETAILACTIONS.JS
export const setSelectedProduct = (product) => {
return {
type: "SET_SELECTED_PRODUCT",
payload: product,
};
};
PRODUCTDETAILREDUCER.JS
const initialState = {
singleProduct: [],
};
export const productDetailReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_SELECTED_PRODUCT":
return {
...state,
singleProduct: action.payload,
};
default:
return state;
}
};
REDUCER INDEX.JS
import { combineReducers } from "redux";
import { aboutUsReducer } from "./aboutus-reducers/AboutUsReducers";
import { footerReducer } from "./footer-reducers/FooterReducer";
import { headerReducer } from "./header-reducers/headerReducer";
import { mainSliderReducer } from "./homepage-reducers/mainSliderReducer";
import { youtubeReducer } from "./homepage-reducers/YoutubeReducer";
import { productDetailReducer } from "./product-detail/ProductDetailReducer";
import { productListReducer } from "./product-list-reducers/ProductListReducer";
import { productSliderData } from "./productslider-reducers/ProductSliderReducer";
import { teamReducer } from "./team-reducers/TeamReducers";
const rootReducer = combineReducers({
header: headerReducer,
mainSlider: mainSliderReducer,
youtube: youtubeReducer,
aboutUs: aboutUsReducer,
productSlider: productSliderData,
team: teamReducer,
footer: footerReducer,
productList: productListReducer,
singleProduct: productDetailReducer,
});
export default rootReducer;
STORE.JS
import { createStore } from "redux";
import rootReducer from "./reducers/index";
const store = createStore(
rootReducer,
{},
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
PRODUCTLISTACTIONS.JS
export const setProductListData = (data) => {
return {
type: "SET_PRODUCT_LIST_DATA",
payload: data,
};
};
PRODUCTLISTREDUCER.JS
const initialState = {
productListData: {},
};
export const productListReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_PRODUCT_LIST_DATA":
return {
...state,
productListData: action.payload,
};
default:
return state;
}
};
DB.JSON
I am fetching productListing from json file which look like this
From what I can see, the ProductDetails component is missing a dependency on the id route param to select the correct products to filter, and I am assuming that since the index file is fetching and populating the store each time it mounts that perhaps the initial products state is null, undefined, or some other non-array value that is missing the filter method.
Add the missing id route param to the useEffect hook's dependency array so products are fetched when the route changes.
const { id } = useParams();
...
useEffect(() => {
getProductId(id);
}, [id]);
Since it appears that products is potentially undefined, i.e. falsey, you should account for then when attempting to filter the array. Provide a fallback empty array to filter from if products is falsey.
const getProductId = (id) => {
window.scrollTo(0, 0);
products ? setIsLoading(false) : setIsLoading(true);
const singleProduct = (products || []).filter((product) => {
return product.id === parseInt(id);
});
dispatch(setSelectedProduct(singleProduct));
};
You didn't include any of your Redux code (i.e. actions/reducers/store configuration) but a suggestion is to always provide a consistent state invariant. For example, always returning an array of products and using the array length and some "isLoading" state to determine when it's safe to filter the state. At a minimum, if it's guaranteed that products is always an array then all array methods will always work as expected, regardless of where the app is in its lifecycle and loading data/state.
Hey I found this solution. I fetched products through async function instead of getting products back from useSelector. Please check ProductDetail Component below.
PRODUCTDETAIL.JS
import React, { useState, useEffect } from "react";
import Cart from "../cart/Cart";
import "./productdetail.css";
import Loading from "../../other/loading/Loading";
import PageNotFound from "../../other/page-not-found/PageNotFound";
import { Link, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { setSelectedProduct } from "../../../redux/actions/product-detail-actions/ProductDetailActions";
const ProductDetail = () => {
const [loading, setLoading] = useState(true);
const { id } = useParams();
const dispatch = useDispatch();
const singleProduct = useSelector(
(state) => state.singleProduct.singleProduct
);
useEffect(() => {
getProductListingData();
}, []);
const getProductListingData = async () => {
try {
const response = await fetch("http://localhost:8000/productListing");
const data = await response.json();
if (data) {
setLoading(false);
getProductID(data.products);
} else {
// setProducts("PRODUCT LISTING DATA NOT FOUND");
setLoading(true);
console.log("PRODUCT LISTING DATA NOT FOUND");
}
} catch (error) {
console.log(error);
}
};
const getProductID = (products) => {
window.scroll(0, 0);
let singleProduct = [];
singleProduct = products.filter((item) => {
return item.id === parseInt(id);
});
dispatch(setSelectedProduct(singleProduct));
};
if (loading) {
return <Loading loadingProductDetail="Loading Product Details" />;
}
console.log(id);
console.log(singleProduct);
return (
<>
<main>
<div className="dvMain">
<div className="container">
<div className="row">
{singleProduct.length > 0 &&
singleProduct.map((product) => {
const {
id,
img,
heading,
description,
pack,
price,
size,
category,
detailsTable,
} = product;
return (
<div key={id} className="col-lg-9">
<div className="row">
<div key={id} className="dvProducts col-12">
<div className="row">
<div className="dvBack col-12">
<Link
to="/products"
className="mb-1 d-inline-block"
>
<i className="fa fa-angle-left"></i>
<span> Back</span>
</Link>
</div>
<div className="col-12">
<div className="row">
<div className="col-12 col-md-6 col-xl-4 mb-3">
<div className="border border-light shadow-sm p-1 h-100 d-flex justify-content-center align-items-center">
<div className="bg-light text-center h-100">
<a className="d-inline-block h-100">
<img
src={img}
className="img-fluid"
alt={heading}
/>
</a>
</div>
</div>
</div>
<div className="col-12 col-md-6 col-xl-8 d-flex mb-3 mb-xl-0">
<div className="m-md-auto">
<div>
<h2 className="heading-2">{heading}</h2>
</div>
<div className="mb-2">
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
<i className="fa fa-star-o text-warning d-inline-block"></i>
</div>
<div className="mb-3">
<p className="paragraph">{description}</p>
</div>
<div className="d-flex mb-3">
<div className="mr-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Pack:
</h5>
<span className="paragraph d-inline-block">
{pack}
</span>
</div>
<div className="mr-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Size:
</h5>
<span className="paragraph d-inline-block">
{size}
</span>
</div>
<div className="mr-2 ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Category:
</h5>
<span className="paragraph d-inline-block">
{category}
</span>
</div>
<div className="ml-2">
<h5 className="heading-5 d-inline-block mb-1 mr-1">
Price:
</h5>
<span className="paragraph d-inline-block">
<i className="fa fa-inr"></i>
<span className="d-inline-block">
{price}.00
</span>
</span>
</div>
</div>
<div>
<button
className="btn btn-black"
href="/-"
>
Add to Bag
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="dvTabs col-12 mt-4">
<div className="row">
<div className="col-12">
<div className="row">
<div className="col-12">
<ul
className="nav nav-tabs"
id="myTab"
role="tablist"
>
<li className="nav-item">
<a
className="heading-5 nav-link active"
id="description-tab"
data-toggle="tab"
href="#description"
role="tab"
aria-controls="description"
aria-selected="true"
>
Description
</a>
</li>
</ul>
</div>
</div>
<div
className="dvTabsContent tab-content row"
id="myTabContent"
>
<div
className="tab-pane fade show active py-3 col-12"
id="description"
role="tabpanel"
aria-labelledby="description-tab"
>
<div className="row">
<div className="col-12">
<div className="table-responsive">
<table className="table table-striped table-bordered">
<thead>
<tr>
<th
className="heading-5"
scope="col"
>
Details
</th>
<th
className="heading-5"
scope="col"
>
Weight
</th>
<th
className="heading-5"
scope="col"
>
1Glass 250ml
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Amount per serving</td>
<td>250ml</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
})}
<Cart id={id} />
</div>
</div>
</div>
</main>
</>
);
};
export default ProductDetail;
friends, I have array of questions, and a dropdown list for them... i want to open any question, but all questions are opening together... please help
const FAQ = () => {
const [isOpenAnswer, setIsOpenAnswer] = useState(false)
const toggle = (id) => {
questions.forEach((q) => {
if(q.id === id){
setIsOpenAnswer((prevState) => !prevState)
}
})
}
return <Layout>
<div className="questionsBox pb-5">
<h2 className="title pt-4 pb-4" >Frequently Asked Questions</h2>
{questions.map((q, index) => {
return <div className="question pl-1 pt-3 pb-3 pr-1" key={index}>
<div className="d-flex justify-content-between">
<span className="questionTitle">{q.question}</span>
<img className="questionIcon"
src={Plus} alt="plus"
onClick={() => toggle(q.id)}
/>
</div>
{isOpenAnswer && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
</div>
})}
</div>
</Layout>
}
Use a Javascript object to track which unique q.id is being set to true.
const FAQ = () => {
const [isOpenAnswer, setIsOpenAnswer] = useState({})
const toggle = (id) => {
setIsOpenAnswer(prevState => ({
...prevState,
[id]: !prevState[id],
});
}
return <Layout>
<div className="questionsBox pb-5">
<h2 className="title pt-4 pb-4" >Frequently Asked Questions</h2>
{questions.map((q, index) => {
return <div className="question pl-1 pt-3 pb-3 pr-1" key={index}>
<div className="d-flex justify-content-between">
<span className="questionTitle">{q.question}</span>
<img className="questionIcon"
src={Plus} alt="plus"
onClick={() => toggle(q.id)}
/>
</div>
{isOpenAnswer[q.id] && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
</div>
})}
</div>
</Layout>
}
You're using the same prop for all of them here:
{isOpenAnswer && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
Try saving something unique in state to identify what you're supposed to be showing, e.g.,
{selectedQuestionId && /* the rest */ }
and set the selectedQuestionId where you're currently setting isOpenAnswer .
I get a network error just some of the times I call a lazyQuery in my React project with Apollo and GraphQL. In my app I can successfully get search results back containing various drinks. These are mapped to their own buttons. On clicking one of these buttons a function triggers that queries data about that specific drink.
Most of the time it works fine, but some of the times I get a status 500 error, saying that the cocktailName variable of the type string was not provided. When I've tried requesting the same drinks several times I've sometimes gotten a good response and sometimes not. When I try to debug the function that sends the query, it seems to receive the variable every single time, and I haven't managed to crash the app when I have debug breakpoints in that function. I'd really appreciate any assistance with this!
Main component for the drinks:
import React from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import { useQuery, useLazyQuery } from '#apollo/react-hooks';
import { RANDOM_COCKTAIL_QUERY, ONE_COCKTAIL_BY_NAME_QUERY } from './Queries';
import LoadingSpinner from './LoadingSpinner';
import CocktailSearchFieldAlt from './CocktailSearchFieldAlt';
import { cocktailIngredientList, cocktailMeasureList } from './ingredientLists';
// Imported ingredient and measure lists represent names of keys in objects
export default function Cocktails() {
const dataType = useStoreState((state) => state.cocktailDataType);
const changeDataType = useStoreActions((actions) => actions.changeCocktailDataType);
const dataSet = useStoreState((state) => state.cocktailDataSet);
const changeDataSet = useStoreActions((actions) => actions.changeCocktailDataSet);
const randomizeClick = () => {
if (dataType !== 'randomCocktail') {
changeDataType('randomCocktail');
changeDataSet('data');
}
refetch();
};
const specifikLookup = (drinkName) => {
if (dataType === 'randomCocktail') {
changeDataSet('specificData');
changeDataType('oneCocktailByName');
}
getDrink({ variables: { cocktailName: drinkName } });
};
const { loading, error, data, refetch } = useQuery(RANDOM_COCKTAIL_QUERY);
const [
getDrink,
{ data: specificData, loading: loadingSpecific, error: errorSpecific }
] = useLazyQuery(ONE_COCKTAIL_BY_NAME_QUERY);
if (loading || loadingSpecific) return <LoadingSpinner />;
if (error) return `Error! ${error}`;
if (errorSpecific) return `Error! ${errorSpecific}`;
return (
<section id="cocktail">
<h2 className="mainHeader mt-5 mb-4 scrollTrick">Cocktail</h2>
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-9">
<h4>{eval(dataSet)[dataType].strDrink}</h4>
<p>
{eval(dataSet)[dataType].strAlcoholic}
<br />
Best served in: {eval(dataSet)[dataType].strGlass}
</p>
<h6>Recipee</h6>
<div className="row">
<div className="col-md-4">
<ul>
{eval(dataSet) &&
cocktailIngredientList.map(
(x, i) =>
eval(dataSet)[dataType][x] && (
<li key={i}>{eval(dataSet)[dataType][x]}</li>
)
)}
</ul>
</div>
<div className="col-md-3">
<ul>
{data &&
cocktailMeasureList.map(
(x) => eval(dataSet)[dataType][x] && <li>{eval(dataSet)[dataType][x]}</li>
)}
</ul>
</div>
</div>
<h6>Instructions</h6>
<p>{eval(dataSet)[dataType].strInstructions}</p>
</div>
<div className="col-md-3">
<img src={eval(dataSet)[dataType].strDrinkThumb} alt="Cocktail" className="img-fluid mb-3" />
<button className="btn btn-primary btn-block" onClick={() => randomizeClick()}>
Randomize Cocktail
</button>
</div>
</div>
{/* <CocktailSearchField specificLookup={specifikLookup} /> */}
<CocktailSearchFieldAlt specificLookup={specifikLookup} />
</div>
</section>
);
}
Search component for the drinks:
import React, { useState } from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import { useLazyQuery } from '#apollo/react-hooks';
import { MULTIPLE_COCKTAILS_BY_NAME_QUERY, MULTIPLE_COCKTAILS_BY_INGREDIENT_QUERY } from './Queries';
import LoadingSpinner from './LoadingSpinner';
export default function SearchField(props) {
const [
searchInput,
setSearchInput
] = useState('');
const drinkOptionValue = useStoreState((state) => state.drinkOptionValue);
const changeDrinkOptionValue = useStoreActions((actions) => actions.changeDrinkOptionValue);
const handleSubmit = (e) => {
e.preventDefault();
getDrinks({ variables: { cocktailName: searchInput } });
getDrinksByIngredient({ variables: { ingredientName: searchInput } });
};
const [
getDrinks,
{ loading, data }
] = useLazyQuery(MULTIPLE_COCKTAILS_BY_NAME_QUERY);
const [
getDrinksByIngredient,
{ loading: loadingByIngredient, data: dataByIngredient }
] = useLazyQuery(MULTIPLE_COCKTAILS_BY_INGREDIENT_QUERY);
const lookupDrink = (e) => {
console.log(e.target.value);
changeDrinkOptionValue(e.target.value);
props.specificLookup(e.target.value);
};
if (loading || loadingByIngredient) return <LoadingSpinner />;
return (
<div>
<div className="row border-top mt-3 justify-content-center">
<div className="mt-3">
<form className="form-inline my-2 my-lg-0" onSubmit={handleSubmit}>
<input
className="form-control mr-sm-1"
type="text"
placeholder="Search cocktail"
onChange={(e) => setSearchInput(e.target.value)}
value={searchInput}
/>
<button className="btn btn-secondary my-2 my-sm-0 pl-3 pt-2 pb-2 pr-3" type="submit">
<i className="material-icons mt-2">search</i>
</button>
</form>
</div>
</div>
{data &&
(data.multipleCocktailsByName !== null ? (
<div className="container">
<div className="row">
<div className="col">
<h6 className="mt-3 mb-3 text-center">Search results: </h6>
</div>
</div>
<div className="row justify-content-center">
{dataByIngredient.multipleCocktailsByIngredient !== null &&
dataByIngredient.multipleCocktailsByIngredient.map((cocktailByIngredient) => {
if (
data.multipleCocktailsByName.some(
(cocktail) => cocktail.strDrink === cocktailByIngredient.strDrink
)
) {
return;
} else {
data.multipleCocktailsByName.push(cocktailByIngredient);
}
})}
{
(data.multipleCocktailsByName.sort(
(a, b) => (a.strDrink > b.strDrink ? 1 : b.strDrink > a.strDrink ? -1 : 0)
),
data.multipleCocktailsByName.map((cocktail, i) => (
<button
className="btn btn-outline-secondary p-1 menuButton btnAnimated mr-1 mb-1 border border-secondary"
key={i}
value={cocktail.strDrink}
onClick={lookupDrink}
>
<img src={cocktail.strDrinkThumb} className="menuPicture" alt="cocktail" />
{cocktail.strDrink}
</button>
)))
}
</div>
</div>
) : dataByIngredient.multipleCocktailsByIngredient !== null ? (
<div className="container">
<div className="row">
<div className="col">
<h6 className="mt-3 mb-3 text-center">Search results: </h6>
</div>
</div>
<div className="row justify-content-center">
{dataByIngredient.multipleCocktailsByIngredient.map((cocktail, i) => (
<button
className="btn btn-outline-secondary p-1 menuButton btnAnimated mr-1 mb-1 border border-secondary"
key={i}
value={cocktail.strDrink}
onClick={lookupDrink}
>
<img src={cocktail.strDrinkThumb} className="menuPicture" alt="cocktail" />
{cocktail.strDrink}
</button>
))}
</div>
</div>
) : (
<div className="row justify-content-center">
<p>No matching result</p>
</div>
))}
</div>
);
}
I am trying to refactor this code to a functional component so I dont need to wrap this in a Product Consumer. I keep getting error unexpected token in my functional component and can't figure out why.
This is the original
<ProductConsumer>
{value => {
return value.clothing.map(product => {
return <Product key={product.id} product={product} />;
});
}}
</ProductConsumer>
This is the functional Component
const ProductListComponent = (props) => {
const [loading, setloading] = useState(true)
const productConsumer = useContext(ProductContext);
const { cart } = productConsumer;
useEffect(() => {
(async () => {
await domLoaded;
setTimeout(() => {
console.log('DOM is loaded');
setLoading(false);
console.log(loading)
}, 200);
})();
}, [domLoaded, loading, params, props.location.pathname]);
if (loading === false) {
return (
<React.Fragment>
<Slide>
<header className="bg py-5 mb0 container-fluid clothing ">
<div className="container h-100">
<div className="row h-100 align-items-center">
<div className="col-lg-12">
<h1 className="display-4 text-white mt-5 mb-2 text-center">
{props.title}
</h1>
<p
style={props.textStyle}
className="lead mb-5 text-white text-center"
>
{props.description}
</p>
</div>
</div>
</div>
</header>
<div className="py-0 ">
<div className="container">
<div className="row">
return props.items.productConsumer.map(product => {
return <Product key={product.id} product={product} />;
});
</div>
</div>
</div>
</Slide>
</React.Fragment>
)
}else if (loading === true) {
return <Spinner />;
}
}
export default withRouter(ProductListComponent)
I decided to pass in the value.clothing as props so i can reuse the component
The body of a functional component is just a Function, you don't need the wrapping curly brackets:
function Component(props) {
return props.items.productConsumer.map(product => {
return <Product key={product.id} product={product} />;
});
}
This is the correct answer.
{props.items.productConsumer.map(product => {
return <Product key={product.id} product={product} />;
})}