How to link the state of two react components conditionally - javascript

I am making a simple website in React that has two like buttons.
Each like button is in different Card components and both of these are in a parent Projects component.
I want the total number of likes on the website not to exceed and 10. so button one registered 4 likes the second one cannot register more than 6 likes.
This is my initial code and it just gives the same state for both Card components
import styles from "./Card.module.css";
const Card = ({ img, title, description, ytLink, children}) => {
return (
<div className={styles.card}>
<img className={styles["card-media"]} alt={img.alt} {...img} />
<div className={styles["card-details"]}>
<h2 className={styles["card-head"]}>{title}</h2>
<p>{description}</p>
<div className={styles.buttons}>
<a
key={ytLink}
className={styles["card-action-button"]}
href={ytLink}
target="_blank"
>
Watch
</a>
{children}
</div>
</div>
</div>
);
};
export default Card;
import Card from "../Card";
import styles from "./Projects.module.css";
import React, { useState, useEffect } from "react";
import Likebutton from "../Like";
const Projects = ({ projects }) => {
const [likesCount, setLikesCount] = useState(0);
const addLike =() => {
const updateLikes = likesCount + 1;
updateLikes < 10 ? setLikesCount(updateLikes) : setLikesCount("10+");
}
const like=<Likebutton addLike={addLike} likesCount={likesCount}></Likebutton>
return (
<div className={styles.container}>
{projects.map((project) => (
<Card
key={project.title}
{...project}
>{like}</Card>
))}
</div>
);
};
export default Projects;

const Likes = () => {
const [likesA, setLikesA] = useState(0);
const [likesB, setLikesB] = useState(0);
const [message, setMessage] = useState();
const addLike =(like) => {
if(likesA + likesB < 10){
if(like == "A")
{
setLikesA(likesA +1)
}
if(like == "B")
{
setLikesB(likesB +1)
}
}
else{
setMessage("Limit was exceeded")
}
}
return (
<>
<div onClick = {() => addLike("A")}>
Add Like A
</div>
<div onClick = {() => addLike("B")}>
Add Like B
</div>
{message && message}
</>
);
};

Related

why every time is visit my product details page it is auto scrolling to the bottom of the page?

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

Get JSX from imported React Component

For the purposes of documenting our library, I want to convert a React Component function to JSX. For example, if we have a rendered Button component, I want to show the code for how it's constructed.
One solution could be to read Button.jsx as plain text but I feel like there should be a better solution.
// Button.jsx
export const Button = (props) => (
<button {...props}><Icon name={props.icon}/>{props.children}</button>
)
// ButtonDocs.jsx
import { Button } from 'components/Button';
const Docs = (props) => {
const renderedButton = <Button icon='home'>Hello</Button>
// Here I'd expect this function to return something like:
// `<button><Icon name="home"/>Hello</button>`
const buttonJSX = someFunctionToGetJSX(renderedButton)
return (
<div>
{renderedButton}
<code>
{buttonJSX}
</code>
</div>
)
}
Do this in Button component
const Button = (props) => {
const buttonRef = useRef();
const [jsxEl, setJsxEl] = useState("");
useEffect(() => {
let jsxArray = [];
for (let i = 0; i < buttonRef.current.children.length; i++) {
jsxArray.push(`${buttonRef.current.children[i].innerHTML}`);
}
jsxArray.join(",");
setJsxEl(jsxArray);
props.onJsxFunc(jsxEl);
}, [buttonRef]);
return (
<Fragment>
<div ref={buttonRef}>
<button {...props}>
<Icon name={props.icon} />
{props.children}
</button>
</div>
</Fragment>
);
};
export default Button;
Then in ButtonDocs component do the below.
// ButtonDocs.jsx
import { Button } from 'components/Button';
const Docs = (props) => {
const renderedButton = <Button onJsxFunc={(el) => showJsxElements(el)} icon='home'>Hello</Button>
const jsxCode = useRef();
const showJsxElements = (el) => {
jsxCode.current.innerText += `${el}`;
};
return (
<div>
{renderedButton}
<code>
<div ref={jsxCode}></div>
</code>
</div>
)
}

Material UI Pagination

I don't understand why my page can't recognize other pages when I click (for example on page 2, the same page appears again and again)
This is in MealNew.js component:
import React, {useEffect, useState } from "react";
import './MealNew.css';
import Card from "../UI/Card";
import AppPagination from "./AppPagination";
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query,setQuery] = useState('');
const[page,setPage] = useState(9);
const[numberOfPages,setNumberOfPages]= useState(10);
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=991fbfc719c743a5896bebbd98dfe996&page=${page}`;
fetch (link)
.then ((response)=> response.json())
.then ((data) => {
setData(data.results)
setNumberOfPages(data.total_pages)
const elementFood = data?.map((meal,key) => {
return (<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image}
alt='e-meal'/>
</div> )
})
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
}
useEffect(()=> {
handleClick();
},[page])
return (
<Card className="meal">
<form onSubmit={handleSubmit}>
<input
className="search"
placeholder="Search..."
value={query}
onChange={(e)=>setQuery(e.target.value)}/>
<input type='submit' value='Search'/>
</form>
<li className="meal">
<div className = 'meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination
setPage={setPage}
pageNumber={numberOfPages}
/>
</div>
</li>
</Card>
) }
export default MealNew;
This is in AppPagination.js component:
import React from "react";
import { Pagination } from "#mui/material";
const AppPagination = ({setPage,pageNumber}) => {
const handleChange = (page)=> {
setPage(page)
window.scroll(0,0)
console.log (page)
}
return (
<div >
<div >
<Pagination
onChange={(e)=>handleChange(e.target.textContent)}
variant="outlined"
count={pageNumber}/>
</div>
</div>
)
}
export default AppPagination;
Thanks in advance, I would appreciate it a lot
The only error I am getting in Console is this:
Line 64:3: React Hook useEffect has a missing dependency: 'handleClick'. Either include it or remove the dependency array react-hooks/exhaustive-deps
You are not following the spoonacular api.
Your link looks like this:
https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&page=${page}
I checked the spoonacular Search Recipes Api and there's no page parameter you can pass. You have to used number instead of page.
When you receive response from the api, it returns the following keys: offset, number, results and totalResults.
You are storing totalResults as totalNumberOfPages in state which is wrong. MUI Pagination count takes total number of pages not the total number of records. You can calculate the total number of pages by:
Math.ceil(totalRecords / recordsPerPage). Let say you want to display 10 records per page and you have total 105 records.
Total No. of Pages = Math.ceil(105/10)= 11
Also i pass page as prop to AppPagination component to make it as controlled component.
Follow the documentation:
Search Recipes
Pagination API
Complete Code
import { useEffect, useState } from "react";
import { Card, Pagination } from "#mui/material";
const RECORDS_PER_PAGE = 10;
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const [numberOfPages, setNumberOfPages] = useState();
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&number=${page}`;
fetch(link)
.then((response) => response.json())
.then((data) => {
setData(data.results);
const totalPages = Math.ceil(data.totalResults / RECORDS_PER_PAGE);
setNumberOfPages(totalPages);
});
};
const elementFood = data?.map((meal, key) => {
return (
<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image} alt='e-meal' />
</div>
);
});
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
};
useEffect(() => {
handleClick();
console.log("first");
}, [page]);
return (
<Card className='meal'>
<form onSubmit={handleSubmit}>
<input className='search' placeholder='Search...' value={query} onChange={(e) => setQuery(e.target.value)} />
<input type='submit' value='Search' />
</form>
<li className='meal'>
<div className='meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination setPage={setPage} pageNumber={numberOfPages} page={page} />
</div>
</li>
</Card>
);
};
const AppPagination = ({ setPage, pageNumber, page }) => {
const handleChange = (page) => {
setPage(page);
window.scroll(0, 0);
console.log(page);
};
console.log("numberOfPages", pageNumber);
return (
<div>
<div>
<Pagination
page={page}
onChange={(e) => handleChange(e.target.textContent)}
variant='outlined'
count={pageNumber}
/>
</div>
</div>
);
};
export default MealNew;

How to add a "show more" button to each card on React?

I have React component:
Main.jsx
import { useState, useEffect } from "react";
import { Preloader } from "../Preloader";
import { Pokemons } from "../Pokemons";
import { LoadMore } from "../LoadMore";
function Main() {
const [pokemons, setPokemons] = useState([]);
const [loading, setLoading] = useState(true);
const [pokemonsPerPage] = useState(20);
const [page, setPage] = useState(1);
function getPokemons(pokemonOffset) {
fetch(
`https://pokeapi.co/api/v2/pokemon?limit=${pokemonsPerPage}&offset=${pokemonOffset}`
)
.then((responce) => responce.json())
.then((data) => {
data.results && setPokemons((p) => [...p, ...data.results]);
setLoading(false);
});
}
useEffect(() => {
const offset = page * pokemonsPerPage - pokemonsPerPage;
getPokemons(offset);
}, [page]);
return (
<main className="container content">
{loading ? <Preloader /> : <Pokemons pokemons={pokemons} />}
<LoadMore next={() => setPage((p) => p + 1)} />
</main>
);
}
export { Main };
Pokemon.jsx
import { useState, useEffect } from "react";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
return (
<div>
{data ? (
<div className="card animate__animated animate__fadeIn">
<div className="card-image">
<img src={data.sprites.front_default} />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
) : (
<div>loading...</div>
)}
</div>
);
}
export { Pokemon };
I need each card (Pokemon) to have a "Details" button, which, when clicked, displays additional (unique) information from the fetch request in the "url" for the selected card
I think I need to do this in Pokemon.jsx but I just started learning React and haven't come across a similar challenge
If you just need a button for each card I would assume this
{data.map((item, index) =>
<div key={index}>
....
<button onClick={()=> { do something }}>
</div>
)}
and then create a function that fetches data and add it to your array where you keep
the data and might have to mess with the useEffect when you want to see the change.

i want to increment and decrement the number when the button from other component is clicked

counterScreen.js
import React, { useState } from "react";
const CounterScreen = () => {
const [count, setCount] = useState(0);
return (
<div>
<h2>This the number: {count}</h2>
</div>
) }
export default CounterScreen
addButton.js
import React from 'react'
const AddButton = () => {
return (
<div>
<button>+</button>
</div>
) }
export default AddButton
subtractButton.js
import React from 'react'
const SubtractButton = () => {
return (
<div>
<button>-</button>
</div>
) }
export default SubtractButton
i want when i click the button in addbutton.js the counter should add 1 and when i click the button in subtractbutton.js the counter should subtract 1
what will be the best way to share the state here please help
One simple way to solve this is to put the state in the containing component, and pass in values or callbacks to the relevant components:
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<CounterScreen count={count}/>
<AddButton onClick={() => setCount(count+1)}/>
<SubtractButton onClick={() => setCount(count-1)}/>
</div>
);
};
const CounterScreen = ({count}) => {
return (
<div>
<h2>This the number: {count}</h2>
</div>
)
};
const AddButton = ({onClick}) => {
return (
<div>
<button onClick={onClick}>+</button>
</div>
)
};
const SubtractButton = ({onClick}) => {
return (
<div>
<button onClick={onClick}>-</button>
</div>
)
};

Categories