I am trying to refactor This code to a functional component - reactjs

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} />;
})}

Related

TypeScript how to write this more pragmatic?

Can I write this code more pragmatic somehow (mainly the RenderMenu I want to improve, but suggestions on both is appreciated)? I can't really think of how. I'm not used to TypeScript. I'm rendering a food-menu from JSON (import {data} from './DinnerData'. First I render the title e.g. "Starters" then it goes through all the starters and rendering each dinner with two map functions.
export const RenderMenu = (props: Props) => {
return (
<>
{data.types.map((type, index) => {
return (
<>
<div className="row">
<div className="col-xl-12">
<div className="section-title text-center">
<h4 key={index}>
{type.name}
</h4>
</div>
</div>
</div>
<div className="row menu_style1">
{type.items.map((item, index) => {
return (
<>
<Dinner title={item.name} price={item.price} children={item.text} image={"https://i.imgur.com/kbpceNv.jpg"} />
</>
)
})}
</div>
</>
)
})}
</>
);
};
Here is the Menu itself.
export const Menu = (props: Props) => {
return (
<>
<section className="about-area pt-60 m-2 p-2" id="home">
<div className="container mb-5">
<div className="row">
<div className="col-xl-12 mb-60">
<div className="section-title text-center">
<p></p>
<h1>Vår meny</h1>
</div>
</div>
</div>
<RenderMenu/>
</div>
</section>
</>
);
};
You can extract smaller components.
MenuTitle
export const MenuTitle = (props) => {
const { name, key } = props;
return (
<div className="row">
<div className="col-xl-12">
<div className="section-title text-center">
<h4 key={key}>{name}</h4>
</div>
</div>
</div>
);
};
MenuItems
export const MenuItems = (props) => {
const { items } = props;
return (
<div className="row menu_style1">
{items.map((item, index) => {
<>
<Dinner
title={item.name}
price={item.price}
children={item.text}
image={"https://i.imgur.com/kbpceNv.jpg"}
/>
</>;
})}
</div>
);
};
RenderMenu is now more cleaner & easier on the head.
export const RenderMenu = (props) => {
return (
<>
{data.types.map((type, index) => {
return (
<>
<MenuTitle name={type.name} key={index} />
<MenuItems items={type.items} />
</>
);
})}
</>
);
};

reactJS doesn't render the data after being fetched in useEffect hook

I have an API using axios which will fetch a list of products. I have called it inside useEffect() hook with [] as the second argument. I check the data before running into return command of the functional component and the data is there, but my ProductCard component are not rendered. Could you please help me with this?
FeaturedProducts.js
const FeaturedProducts = (props) => {
const [productList, setProductList] = useState([]);
// const productList = useSelector(productListSelector);
useEffect(async () => {
let rs = await fetchFeaturedProducts();
setProductList(rs);
}, []);
console.log(productList);
return (
<div className="container-fluid pt-5">
<div className="text-center mb-4">
<h2 className="section-title px-5"><span className="px-2">Featured</span></h2>
</div>
<div className="row px-xl-5 pb-3">
{
productList.map(product => {
<ProductCard id={product.id} name={product.name} price={product.price} img="abc"/> // need img={product.img}
})
}
</div>
</div>
);
}
export default FeaturedProducts;
fetchFeaturedProducts()
const fetchFeaturedProducts = async () => {
try {
let response = await AxiosClient.post("/product/search", {
searchType: "desc",
searchBy: "average_rating"
});
let data = response.data.data;
return data;
}
catch (error) {
raiseErrorMessages(error.response.data.errors);
return [];
}
}
ProductCard.js
const ProductCard = (props) => {
const detailUrl = "/product?id=" + toString(props.id);
return (
<div className="col-lg-3 col-md-6 col-sm-12 pb-1">
<div className="card product-item border-0 mb-4">
<div className="card-header product-img position-relative overflow-hidden bg-transparent border p-0">
<img className="img-fluid w-100" src={props.img} alt="" />
</div>
<div className="card-body border-left border-right text-center p-0 pt-4 pb-3">
<h6 className="text-truncate mb-3">{props.name}</h6>
<div className="d-flex justify-content-center">
<h6>{props.price}</h6>
{/* <h6 className="text-muted ml-2"><del>{props.price}</del></h6> */}
</div>
</div>
<div className="card-footer d-flex justify-content-between bg-light border">
<i className="fas fa-eye text-primary mr-1" />View Detail
<i className="fas fa-shopping-cart text-primary mr-1" />Add To Cart
</div>
</div>
</div>
)
}
export default ProductCard;
Thank you for your precious time.
that happens because you missed a return when you mapped your data
// .....
{
productList.map(product => {
return (<ProductCard id={product.id} name={product.name}
price={product.price} img="abc"/>) // need img=
{product.img}
})
}
or you can use ()
{
productList.map(product => (
<ProductCard id={product.id} name={product.name}
price={product.price} img="abc"/>) // need img=
{product.img})
}

Cannot redirect one page to another page in ReactJs

I'm making a function that when i click on the image container, it will open the page with the product detail with the exact detail for each specific product. However, when i click on the image, nothing happen! Please help me to find out what wrong with my codes, thank you so much!
Product.js:
class Product extends React.Component {
render() {
const { id, title, img, price, inCart } = this.props.product;
return (
<ProductWrapper clasName="col-9 mx-auto col-md-6 col-lg-3 my-3">
<div className="card">
<ProductContext.Consumer>
{(value) => (
<div className="img-container p-5">
<Router>
<Link to="/details">
<img
src={img}
alt="product"
className="card-img-top"
onClick={() => {
value.handleDetail(id);
}}
/>
</Link>
</Router>
<button
className="cart-btn"
onClick={() => value.addToCart(id)}
disabled={inCart ? true : false}
>
{inCart ? (
<p className="text-capitalize mb-0">In Cart</p>
) : (
<i class="fas fa-cart-plus"></i>
)}
</button>
</div>
)}
</ProductContext.Consumer>
<div className="card-footer d-flex justify-content-between">
<p className="align-self-center mb-0">{title}</p>
<h5 className="text-blue mb-0">
<span className="mr-1">$</span>
{price}
</h5>
</div>
</div>
</ProductWrapper>
);
}
}
context.js:
class ProductProvider extends React.Component {
state = {
products: storeProducts,
detailProduct: detailProduct
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
handleDetail = (id) => {
const product = this.getItem(id);
this.setState(() => {
return { detailProduct: product };
});
};
addToCart = (id) => {
console.log(`hello details. id is ${id}`);
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleDetail: this.handleDetail,
addToCart: this.addToCart
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
Sandbox link for better observation: https://codesandbox.io/s/why-cant-i-fetch-data-from-a-passed-value-forked-30bgi?file=/src/App.js
I recommend a different approach where the product ID goes into the URL rather than being selected in context. This has a major advantage in that refreshing the details page means the product ID will be retained.
Here is a link to a working CodeSandbox.
And here are the changes I made:
In the context provider, you can remove handleDetail since the selection will instead live in the URL:
class ProductProvider extends React.Component {
state = {
products: storeProducts,
detailProduct: detailProduct
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
addToCart = (id) => {
console.log(`hello details. id is ${id}`);
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
addToCart: this.addToCart
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
In the App component, change your details route to take an itemId parameter:
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ProductProvider>
<Router>
<Switch>
<Route exact path="/productlist" component={ProductList} />
<Route path="/details/:itemId" component={Details} />
<Route path="*" component={() => "404 Not Found"} />
</Switch>
</Router>
</ProductProvider>
</div>
);
}
In your product component, Make the Link point to the details/itemId URL and remove any need to set that ID in context:
class Product extends React.Component {
render() {
const { id, title, img, price, inCart } = this.props.product;
return (
<ProductWrapper clasName="col-9 mx-auto col-md-6 col-lg-3 my-3">
<div className="card">
<ProductContext.Consumer>
{(value) => (
<div className="img-container p-5">
<Link to={`/details/${id}`}>
<img src={img} alt="product" className="card-img-top" />
</Link>
<button
className="cart-btn"
onClick={() => value.addToCart(id)}
disabled={inCart ? true : false}
>
{inCart ? (
<p className="text-capitalize mb-0">In Cart</p>
) : (
<i class="fas fa-cart-plus"></i>
)}
</button>
</div>
)}
</ProductContext.Consumer>
<div className="card-footer d-flex justify-content-between">
<p className="align-self-center mb-0">{title}</p>
<h5 className="text-blue mb-0">
<span className="mr-1">$</span>
{price}
</h5>
</div>
</div>
</ProductWrapper>
);
}
}
Finally, in the Details component, pluck the itemId off of the params and find the right item from your product list in context:
class Details extends React.Component {
render() {
const { itemId } = this.props.match.params;
return (
<ProductContext.Consumer>
{(value) => {
const selected = value.products.find(
(p) => p.id === parseInt(itemId)
);
if (!selected) {
return "Bad product ID: " + itemId;
}
const { id, company, img, info, price, title, inCart } = selected;
return (
<div className="container py-5">
{/* title */}
<div className="row">
<div className="col-10 mx-auto text-center text-slanted text-blue my-5">
<h1>{title}</h1>
</div>
</div>
{/* end of title */}
<div className="row">
<div className="col-10 mx-auto col-md-6 my-3">
<img src={img} className="img-fluid" alt="" />
</div>
{/* prdoduct info */}
<div className="col-10 mx-auto col-md-6 my-3 text-capitalize">
<h1>model : {title}</h1>
<h4 className="text-title text-uppercase text-muted mt-3 mb-2">
made by : <span className="text-uppercase">{company}</span>
</h4>
<h4 className="text-blue">
<strong>
price : <span>$</span>
{price}
</strong>
</h4>
<p className="text-capitalize font-weight-bold mt-3 mb-0">
some info about product :
</p>
<p className="text-muted lead">{info}</p>
{/* buttons */}
<div>
<Link to="/productlist">
<ButtonContainer>back to products</ButtonContainer>
</Link>
<ButtonContainer
cart
disabled={inCart ? true : false}
onClick={() => {
value.addToCart(id);
}}
>
{inCart ? "in cart" : "add to cart"}
</ButtonContainer>
</div>
</div>
</div>
</div>
);
}}
</ProductContext.Consumer>
);
}
}

How to open one dropdown item?

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 .

Irregular status 500 on Apollo GraphQL requests ("Variable X of required type \"String!\" was not provided.")

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>
);
}

Resources