NEXTJS Conditional Statement not working inside of a map - reactjs

When I compile this code, it gives me an error saying that I am not properly closing the a tag.
When I remove the conditional statement on the following code, it works fines:
{nid[index] != undefined ? (
<a target= "_blank" rel="noreferrer" href={polygonURL + Cont[index] + "/" + nid[index]}>
) : (
<a target= "_blank" rel="noreferrer" href={unkpolygonURL + Cont[index]}>
)}
import styles from "../styles/Home.module.css";
import React, { useEffect, useState, useRef } from "react";
import { useAddress, useMetamask, useDisconnect } from "#thirdweb-dev/react";
import Spinner from "./Spinner";
export default function NFT() {
const address = useAddress();
let image = "";
let total = 0;
const [title, setTitle] = useState([]);
const [Img, setImg] = useState([]);
const [Cont, setCont] = useState([]);
const [nid, setNid] = useState([]);
const[totalNFT, setTotalNFT] = useState(0);
let page = "";
const polygonURL = "https://opensea.io/assets/matic/";
const unkpolygonURL = "https://opensea.io/assets?search[query]=";
const getPolygonNFT = async (str) => {
// const api = 'https://eth-mainnet.alchemyapi.io/v2/demo/getNFTs/?owner=0xfae46f94ee7b2acb497cecaff6cff17f621c693d';
const api = `https://polygon-mainnet.g.alchemyapi.io/v2/demo/getNFTs/?owner=` + str;
const response = await fetch(api);
const data = await response.json();
console.log(data);
let total = data.totalCount;
let firsttotal = data.ownedNfts.length;
setTotalNFT(data.ownedNfts.length);
// setPage(data.pageKey);
let page = data.pageKey;
for (var i = 0; i < firsttotal; i++){
setImg(oldArray => [...oldArray, data.ownedNfts[i].media[0].gateway]);
setTitle(oldArray => [...oldArray, data.ownedNfts[i].metadata.name]);
setCont(oldArray => [...oldArray, data.ownedNfts[i].contract.address]);
setNid(oldArray => [...oldArray, data.ownedNfts[i].metadata.edition])
}
let j = true;
let count = 0;
while (j){
if (page != undefined){
const api = `https://polygon-mainnet.g.alchemyapi.io/v2/demo/getNFTs/?owner=` + str + `&pageKey=` + page;
const response = await fetch(api);
const data = await response.json();
console.log(data)
page = data.pageKey;
let nexttotal = data.ownedNfts.length;
for (var k = count; k < nexttotal+count ; k++){
setImg(oldArray => [...oldArray, data.ownedNfts[k].media[0].gateway]);
setTitle(oldArray => [...oldArray, data.ownedNfts[k].metadata.name]);
setCont(oldArray => [...oldArray, data.ownedNfts[k].contract.address]);
setNid(oldArray => [...oldArray, data.ownedNfts[k].metadata.edition])
}
} else {
j = false;
}
}
}
useEffect(() => {
getPolygonNFT(address);
}, []);
return (
<main className="inside p-10 shadow-xl shadow-gray-400/20">
{totalNFT === 0 ? (
<>
{totalNFT !== 0 ? (
<h1 className="mb-10 text-4xl font-extralight" >
<span className='text-slate-300 font-extrabold underline decoration-neutral-600'>You do not own any NFTs.</span>
</h1>
) : (
<Spinner />
)}
</>
) : (
<>
<div className="grid space-x-3 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
{title.map ( (name, index) => {
return (
<div name={index} key={index}>
{nid[index] != undefined ? (
<a target= "_blank" rel="noreferrer" href={polygonURL + Cont[index] + "/" + nid[index]}>
) : (
<a target= "_blank" rel="noreferrer" href={unkpolygonURL + Cont[index]}>
)}
<div className="flex flex-col items-center cursor-pointer transition-all duration-200 hover:scale-105">
<img className="h-80 w-80 rounded-2xl object-cover" src={Img[index]} alt=""
onError={({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src="https://user-images.githubusercontent.com/24848110/33519396-7e56363c-d79d-11e7-969b-09782f5ccbab.png";
}}
/>
<div className="p-5">
<h2 className="text-gray-100 text-3xl">{title[index]}</h2>
<p className="mt-2 text-sm text-gray-400">{Cont[index]}</p>
</div>
</div>
</a>
</div>
)
})
}
</div>
</>
)
}
</main>
)}

import styles from "../styles/Home.module.css";
import React, { useEffect, useState, useRef } from "react";
import { useAddress, useMetamask, useDisconnect } from "#thirdweb-dev/react";
import Spinner from "./Spinner";
export default function NFT() {
const address = useAddress();
let image = "";
let total = 0;
const [title, setTitle] = useState([]);
const [Img, setImg] = useState([]);
const [Cont, setCont] = useState([]);
const [nid, setNid] = useState([]);
const [totalNFT, setTotalNFT] = useState(0);
let page = "";
const polygonURL = "https://opensea.io/assets/matic/";
const unkpolygonURL = "https://opensea.io/assets?search[query]=";
const getPolygonNFT = async (str) => {
// const api = 'https://eth-mainnet.alchemyapi.io/v2/demo/getNFTs/?owner=0xfae46f94ee7b2acb497cecaff6cff17f621c693d';
const api =
`https://polygon-mainnet.g.alchemyapi.io/v2/demo/getNFTs/?owner=` + str;
const response = await fetch(api);
const data = await response.json();
console.log(data);
let total = data.totalCount;
let firsttotal = data.ownedNfts.length;
setTotalNFT(data.ownedNfts.length);
// setPage(data.pageKey);
let page = data.pageKey;
for (var i = 0; i < firsttotal; i++) {
setImg((oldArray) => [...oldArray, data.ownedNfts[i].media[0].gateway]);
setTitle((oldArray) => [...oldArray, data.ownedNfts[i].metadata.name]);
setCont((oldArray) => [...oldArray, data.ownedNfts[i].contract.address]);
setNid((oldArray) => [...oldArray, data.ownedNfts[i].metadata.edition]);
}
let j = true;
let count = 0;
while (j) {
if (page != undefined) {
const api =
`https://polygon-mainnet.g.alchemyapi.io/v2/demo/getNFTs/?owner=` +
str +
`&pageKey=` +
page;
const response = await fetch(api);
const data = await response.json();
console.log(data);
page = data.pageKey;
let nexttotal = data.ownedNfts.length;
for (var k = count; k < nexttotal + count; k++) {
setImg((oldArray) => [
...oldArray,
data.ownedNfts[k].media[0].gateway,
]);
setTitle((oldArray) => [
...oldArray,
data.ownedNfts[k].metadata.name,
]);
setCont((oldArray) => [
...oldArray,
data.ownedNfts[k].contract.address,
]);
setNid((oldArray) => [
...oldArray,
data.ownedNfts[k].metadata.edition,
]);
}
} else {
j = false;
}
}
};
useEffect(() => {
getPolygonNFT(address);
}, []);
return (
<main className="inside p-10 shadow-xl shadow-gray-400/20">
{totalNFT === 0 ? (
<>
{totalNFT !== 0 ? (
<h1 className="mb-10 text-4xl font-extralight">
<span className="text-slate-300 font-extrabold underline decoration-neutral-600">
You do not own any NFTs.
</span>
</h1>
) : (
<Spinner />
)}
</>
) : (
<div className="grid space-x-3 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
{title.map((name, index) => {
return (
<div name={index} key={index}>
{nid[index] != undefined ? (
<a
target="_blank"
rel="noreferrer"
href={polygonURL + Cont[index] + "/" + nid[index]}
>
<div className="flex flex-col items-center cursor-pointer transition-all duration-200 hover:scale-105">
<img
className="h-80 w-80 rounded-2xl object-cover"
src={Img[index]}
alt=""
onError={({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src =
"https://user-images.githubusercontent.com/24848110/33519396-7e56363c-d79d-11e7-969b-09782f5ccbab.png";
}}
/>
<div className="p-5">
<h2 className="text-gray-100 text-3xl">
{title[index]}
</h2>
<p className="mt-2 text-sm text-gray-400">
{Cont[index]}
</p>
</div>
</div>
</a>
) : (
<a
target="_blank"
rel="noreferrer"
href={unkpolygonURL + Cont[index]}
>
<div className="flex flex-col items-center cursor-pointer transition-all duration-200 hover:scale-105">
<img
className="h-80 w-80 rounded-2xl object-cover"
src={Img[index]}
alt=""
onError={({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src =
"https://user-images.githubusercontent.com/24848110/33519396-7e56363c-d79d-11e7-969b-09782f5ccbab.png";
}}
/>
<div className="p-5">
<h2 className="text-gray-100 text-3xl">
{title[index]}
</h2>
<p className="mt-2 text-sm text-gray-400">
{Cont[index]}
</p>
</div>
</div>
</a>
)}
</div>
);
})}
</div>
)}
</main>
);
}

Related

How can i use a hook in a conditional statement?

how can i use a hook like below in an if statement? I am trying use a button click to indicate a change in category. once the change in category is set, pagination aligns the count in accordance to.
const [category, setCategory] = useState('')
let count = ContentsCount
if (category) {
count = filteredContentCount
}
/// ...
<ul className="dropdown-menu">
{categories.map(category => (
<li
style={{
cursor: 'pointer',
listStyleType: 'none'
}}
key={category}
onClick={() => setCategory(category)}
>
<a className='dropdown-item'>{category}</a>
</li>
))}
</ul>
...
{resPerPage <= count && (
<div className="d-flex justify-content-center mt-5">
<Pagination
activePage={currentPage}
itemsCountPerPage={resPerPage}
totalItemsCount={contentsCount}
onChange={setCurrentPageNo}
nextPageText={'Next'}
prevPageText={'Prev'}
firstPageText={'First'}
lastPageText={'Last'}
itemClass="page-item"
linkClass="page-link"
/>
</div>
)}
this is the request in the backend for content and its item counts. ideally the goal is to get total count per category.
exports.getContents = asyncErrors (async (req,res,next) =>{
const contentsCount = await Content.countDocuments(); //162
console.log(contentsCount)
const resPerPage = 9;
const apiFeatures = new APIFeatures(Content.find(),req.query).search().filter().pagination(resPerPage)
let contents = await apiFeatures.query;
console.log(contents)
let filteredContentCount = contents.length;
console.log(filteredContentCount)
/* check for category request
assign total amount of objects within a category to a variable/constant
if(category.req){
let contentCount = category.counter
}
*/
setTimeout(() => {
res.status(200).json({
success:true,
contents,
contentsCount,
resPerPage,
filteredContentCount
})
}, 2000);
})
filtering and pagination:
class APIFeatures {
constructor(query,queryStr){
this.query = query;
this.queryStr = queryStr;
}
search(){
const keyword = this.queryStr.keyword ? {
name:{
$regex:this.queryStr.keyword,
$options:'i'
}
}:{}
console.log(keyword)
this.query = this.query.find({...keyword})
return this;
}
filter(){
const queryCopy = {... this.queryStr}
//removing fields from the query
// console.log(queryCopy)
const removeFields =['keyword','limit','page']
removeFields.forEach(el => delete queryCopy[el]);
// console.log(queryCopy)
//advance filter for title,etc..
let queryStr = JSON.stringify(queryCopy)
queryStr = queryStr.replace(/\b(gt|gte|lt|lte)\b/g,match=>`$${match}`)
console.log(queryCopy)
console.log(queryStr)
this.query = this.query.find(JSON.parse(queryStr));
return this;
}
pagination(resPerPage){
const currentPage = Number(this.queryStr.page) || 1;
const skip = resPerPage * (currentPage-1)
this.query = this.query.limit(resPerPage).skip(skip)
return this
}
}
module.exports = APIFeatures
main react code:
import React, { Fragment, useEffect, useState } from 'react'
import '../App.css'
import { MetaData } from './MetaData'
import { Textarea } from '#nextui-org/react';
import Pagination from 'react-js-pagination'
import ContCard from './Card/ContCard'
import { useDispatch, useSelector } from 'react-redux'
import { getContents } from '../actions/contentActions'
import { useAlert } from 'react-alert'
// import Dropdown from 'react-bootstrap/Dropdown';
import { Dropdown } from 'rsuite';
import { MDBDropdown, MDBDropdownMenu, MDBDropdownToggle, MDBDropdownItem } from 'mdb-react-ui-kit';
import ReactPaginate from 'react-paginate';
export default function Content() {
const [currentPage, setCurrentPage] = useState(1)
const dispatch = useDispatch();
const alert = useAlert()
const [category, setCategory] = useState('')
const { loading, error, contents, contentsCount, resPerPage, filteredContentCount } = useSelector(state => state.contents);
const categories = [
...
]
useEffect(() => {
if (error) {
return alert.error(error)
}
dispatch(getContents(currentPage, category));
}, [dispatch, alert, error, currentPage, category])
function setCurrentPageNo(pageNumber) {
setCurrentPage(pageNumber)
}
// const setCategories = (category)=>{
// setCategory(category)
// }
var count = contentsCount;
console.log('before:' + count)
function setCategories(category) {
// count = filteredContentCount
// console.log(' filtered:' + count)
setCategory(category)
}
console.log('after count:' + count)
return (
<Fragment>
{loading ? <h1>Loading ...</h1> : (
<Fragment>
<section id="contents" className="container mt-5">
<div className="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Categories
</button>
<ul className="dropdown-menu">
{categories.map(category => (
<li
style={{
cursor: 'pointer',
listStyleType: 'none'
}}
key={category}
// onClick={() => setCategory(category)}
onClick={() => setCategories(category)}
>
<a className='dropdown-item'>{category}</a>
</li>
))}
</ul>
</div>
<div className="row" id='cards'>
<Fragment>
<div className="col-6 col-md-9" >
{category}
<div className="row" >
{contents && contents.map(content => (
<ContCard key={content._id} content={content} col={4} />
))}
</div>
</div>
</Fragment>
</div>
</section>
{(resPerPage <= count) && (
<div className="d-flex justify-content-center mt-5">
<Pagination
activePage={currentPage}
itemsCountPerPage={resPerPage}
totalItemsCount={contentsCount}
onChange={setCurrentPageNo}
nextPageText={'Next'}
prevPageText={'Prev'}
firstPageText={'First'}
lastPageText={'Last'}
itemClass="page-item"
linkClass="page-link"
/>
</div>
)}
</Fragment>
)}
</Fragment>
)
}
I am looking to fix pagination. It disappears once you have clicked to set the category button also pagination doesnt keep track of all the possible pages per category.

useEffect runs Mutiple times and upadting the views

I have A Component (the GetSingleVideoID Component) that needs to be rendered every time the id changes
You can check the code on Github the repo Link
the Issue is that the route to get the single video I have put the views of the video to increment when ever the route hits. and then the use Effect keep incrementing the views when I am already on the Component
The getVideoById Route
// #route GET api/v1/video/:id
// #desc GET single video
// #access Private
router.get('/:id', auth, async (req, res) => {
const { id } = req.params;
try {
const videos = await Video.find().populate('channel', ['image', 'title']);
const video = await Video.findById(id).populate('channel', [
'image',
'title',
'subscribers',
'usersToNotify',
]);
if (!video) {
return res.status(404).json({ msg: 'Video not found!' });
}
video.views += 1;
const catVid = {};
video.categoryVids = [];
videos
.filter(
(vid) =>
vid.category === video.category &&
vid._id.toString() !== video._id.toString()
)
.forEach((v) => {
catVid.video = v._id;
catVid.videoTitle = v.title;
catVid.channelTitle = v.channel.title;
catVid.thumb = v.thumbnail;
catVid.views = v.views;
catVid.createdAt = v.createdAt;
video.categoryVids = [...video.categoryVids, catVid];
});
await video.save();
res.status(200).json(video);
} catch (err) {
if (err.kind == 'ObjectId') {
return res.status(400).json({ msg: 'Video not found!' });
}
console.error(err.message);
res.status(500).send('Server Error');
}
});
When I get rid of the func from the dependencies the Subscribe and Notify doesn't work perfectly anymore (like update when i click) I would have to refresh for the changes to work.
import React, { useState, useEffect, useCallback } from 'react';
import moment from 'moment';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useVideoGlobalContext } from '../actions/video';
import { useAuthGlobalContext } from '../actions/auth';
import { useChannelGlobalContext } from '../actions/channel';
import { timeSince, intToString } from '../utils/utilities';
import Section from '../components/Section';
const SingleVideo = () => {
const {
singleVideo,
loading,
getVideoByID,
commentVideo,
likeVideo,
deleteComment,
likes,
unlikes,
unlikeVideo,
deleteVideo,
} = useVideoGlobalContext();
const { user, isAuthenticated, userChannel } = useAuthGlobalContext();
const { id } = useParams();
const {
notifyChannel,
msg,
newChannel,
unnotifyChannel,
subscribeChannel,
unsubscribeChannel,
} = useChannelGlobalContext();
const [text, setText] = useState('');
const [subscribed, setSubscribed] = useState(false);
const [sub, setSub] = useState(false);
const [liked, setLiked] = useState(false);
const [like, setLike] = useState(false);
const [disliked, setDisliked] = useState(false);
const [dislike, setDislike] = useState(false);
const [notified, setNotified] = useState(false);
const [notify, setNotify] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (singleVideo) {
const isSubscribed = singleVideo.channel.subscribers.some(
(cha) => cha.user == user._id
);
setSub(subscribed);
setSubscribed(isSubscribed);
}
}, [singleVideo, subscribeChannel, unsubscribeChannel, newChannel]);
useEffect(() => {
if (singleVideo) {
const isLiked = singleVideo.likes.some((like) => like.user == user._id);
setLike(liked);
setLiked(isLiked);
}
}, [singleVideo, likeVideo, unlikeVideo, newChannel]);
useEffect(() => {
if (singleVideo) {
const isLiked = singleVideo.unlikes.some((like) => like.user == user._id);
setDislike(liked);
setDisliked(isLiked);
}
}, [singleVideo, likeVideo, unlikeVideo, newChannel]);
useEffect(() => {
if (singleVideo) {
const isNotified = singleVideo.channel.usersToNotify.some(
(cha) => cha.user == user._id
);
setNotify(notified);
setNotified(isNotified);
}
}, [singleVideo, notifyChannel, unnotifyChannel, msg]);
// // console.log(data);
useEffect(() => {
getVideoByID(id);
}, [id]);
// useEffect(() => {
// getVideoByID(id);
// }, [getVideoByID, id]);
const onSubscribe = () => {
setSub(!sub);
if (sub && subscribed) {
unsubscribeChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// alert(subMsg.msg);
} else {
subscribeChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// alert(subMsg.msg);
}
};
const onNotification = () => {
setNotify(!notify);
if (notify && notified) {
unnotifyChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
} else {
notifyChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
}
};
const likeAVideo = () => {
setLike(!liked);
if (like && liked) {
alert('You already like this');
} else {
likeVideo(singleVideo._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
}
};
const commentAVideo = (e) => {
e.preventDefault();
commentVideo(singleVideo._id, text);
setText('');
};
const dislikeAVideo = () => {
setDislike(!disliked);
if (dislike && disliked) {
alert('You already unlike this');
} else {
unlikeVideo(singleVideo._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
}
};
const onDelete = (vid) => {
deleteVideo(vid);
navigate('/');
};
return (
<Section nameClass='video-page'>
{singleVideo === null ? (
<h1>Loading....</h1>
) : (
<div class='v-container'>
<div class='video-player'>
<div class='vid-container'>
<video
src={`http://localhost:5000/${singleVideo.videoPath}`}
controls
></video>
{isAuthenticated &&
userChannel &&
userChannel._id === singleVideo.channel._id ? (
<div>
<Link to={`/video/edit/${singleVideo._id}`}>
<button>
<i class='fa-solid fa-pen-to-square'></i>
</button>
</Link>
<button onClick={() => onDelete(singleVideo._id)}>
<i class='fa-solid fa-trash-can'></i>
</button>
</div>
) : (
''
)}
</div>
<div class='vid-info'>
<div class='tags mb-2 primary'>
{singleVideo.tags.map((tag) => (
<span>#{tag} </span>
))}
</div>
<h3>
{singleVideo.title}{' '}
<span class='badge-cate'>{singleVideo.category}</span>
</h3>
<div class='views-date'>
<span class='views'>
{intToString(singleVideo.views)} views
</span>
<span class='date'>
{moment(singleVideo.createdAt).format('MMM Do YYYY')}
</span>
</div>
<div class='desc'>{singleVideo.description}</div>
<div class='channel-card'>
<div class='channel-info'>
<img
src={
singleVideo.channel.image
? `http://localhost:5000/${singleVideo.channel.image}`
: '/img/no-image.png'
}
alt=''
/>
<div class='name-subs'>
<h4>{singleVideo.channel.title}</h4>
<span class='subs'>
{singleVideo.channel.subscribers.length} Subscribers
</span>
</div>
</div>
<div class='btns'>
<button onClick={likeAVideo} class='thumbs'>
<i
class={`fa-solid fa-thumbs-up ${liked ? 'primary' : ''}`}
></i>
</button>
<span>{singleVideo.likes.length}</span>
<button onClick={dislikeAVideo} class='thumbs'>
<i
class={`fa-solid fa-thumbs-down ${
disliked ? 'primary' : ''
}`}
></i>
</button>
<span>{singleVideo.unlikes.length}</span>
<button
onClick={onSubscribe}
className={`sub ${subscribed ? 'disabled' : ''}`}
>
Subscribe
</button>
<button onClick={onNotification} class='bell'>
<i
className={`fa-solid fa-bell ${
notified ? 'primary' : ''
}`}
></i>
</button>
</div>
</div>
<div class='comments'>
<h4>{singleVideo.comments.length} Comments</h4>
<form class='form' onSubmit={commentAVideo}>
<div class='form-control'>
<input
type='text'
name='text'
placeholder='Add a comment...'
class='input'
value={text}
onChange={(e) => setText(e.target.value)}
/>
</div>
<button class='btn'>Add</button>
</form>
<div class='comment-container'>
{singleVideo.comments.length > 0 ? (
singleVideo.comments.map((comment) => (
<div class='comment'>
<img
src={
comment.userImage
? `http://localhost:5000/${comment.userImage}`
: '/img/photo-me.jpeg'
}
alt=''
/>
{isAuthenticated && user._id === comment.user ? (
<button
onClick={() =>
deleteComment(singleVideo._id, comment._id)
}
class='delete'
>
<i class='fa-solid fa-trash-can'></i>
</button>
) : (
''
)}
<div class='info'>
<div>
<h5>{comment.userName}</h5>
<span>
{(timeSince(comment.date) ||
timeSince(comment.date) !== undefined) &&
timeSince(comment.date)}
</span>
</div>
<p>{comment.text}</p>
</div>
</div>
))
) : (
<h3 className='text-center'>No Comments</h3>
)}
</div>
</div>
</div>
</div>
<div class='category-videos'>
<h3>
More Videos in <span class='primary'>{singleVideo.category}</span>
</h3>
<div class='category-vids'>
{singleVideo.categoryVids.length > 0 ? (
singleVideo.categoryVids.map((video) => (
<div class='cat-vid'>
<img
src={
video.thumb
? `http://localhost:5000/${video.thumb}`
: '/img/no-image.png'
}
alt=''
/>
<div class='info'>
<h4>{video.videoTitle}</h4>
<span>
{video.channelTitle} <i class='fa-solid fa-check'></i>{' '}
</span>
<div>
<span class='views'>
{intToString(video.views)} views
</span>
<span class='time'>{timeSince(video.createdAt)}</span>
</div>
</div>
</div>
))
) : (
<h4 className='text-center'>
No Videos for {singleVideo.category}...
</h4>
)}
</div>
</div>
</div>
)}
</Section>
);
};
export default SingleVideo;
on the useEffect I have put the getVideoByID function on the dependencies as you can see on the code above.

Keep track of pagination and go back most recent last page number in react

I am working on an ecommerce, where I am using material UI pagination component for implementing pagination. Here is new requirement arises. I need to add functionality in pagination: if user click on let's say respectively 3,7,11,13 if they click on browser back button they will go back to 11 then 7 then 3 and lastly 1. How do I do that?
I am using react, react router dom.
Here is pagination structure:
FYI, this is url and API structure:
URL and API structure
import React, { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
import ProductList from "../../components/common/ProductList/ProductList";
import {
GET_PRODUCTS_BY_BRAND,
GET_PRODUCTS_BY_CATEGORY,
GET_PRODUCTS_BY_SUBCATEGORY,
GET_PRODUCTS_BY_VENDOR,
} from "../../requests/HomePageApi";
import { Pagination } from "#material-ui/lab";
import "./ShopPage.scss";
const ShopPage = () => {
const { type, slug, subcategory } = useParams();
const [loading, setLoading] = useState(true);
// const [error, setError] = useState(false);
const [brands, setBrands] = useState([]);
const [colors, setColors] = useState([]);
const [sizes, setSizes] = useState([]);
const [products, setProducts] = useState(null);
const [filteredProducts, setFilteredProducts] = useState(null);
const [page, setPage] = React.useState(0);
const [count, setCount] = React.useState(1);
const [limit, setLimit] = React.useState(60);
const [total, setTotal] = React.useState(60);
const [sideFilter, setSideFilter] = useState(false);
const [vandor, setvandor] = useState({
vendorImg: "",
vendorName: "",
vendorSlug: "",
});
const [filter, setFilter] = useState({
// brands: "",
color: "",
size: "",
price: "",
});
const closeSideFilter = () => {
setSideFilter(false);
};
const getProducts = async (slug, qParams) => {
try {
let res;
if (type === "category") {
subcategory
? (res = await GET_PRODUCTS_BY_SUBCATEGORY(
slug,
subcategory,
qParams
))
: (res = await GET_PRODUCTS_BY_CATEGORY(slug, qParams));
}
if (type === "brand") res = await GET_PRODUCTS_BY_BRAND(slug, qParams);
if (type === "store") res = await GET_PRODUCTS_BY_VENDOR(slug, qParams);
if (res) setLoading(false);
if (res && res.products && res.products.length > 0) {
setProducts(res.products);
setFilteredProducts(res.products);
setTotal(res.total);
setCount(Math.ceil(res.total / limit));
if (type === "brand") {
setvandor({
vendorImg: `/assets/images/brand/${res.products[0].brand_logo}`,
vendorName: res.products[0].brand_name,
vendorSlug: res.products[0].brand_slug,
});
} else if (type === "store") {
setvandor({
vendorImg: `/assets/images/brand/${res.products[0].brand_logo}`,
vendorName: res.products[0].shop_name,
vendorSlug: res.products[0].vendorSlug,
});
}
if (res.colors) {
const uniqueColors = [...new Set(res.colors)];
setColors(uniqueColors);
}
if (res.sizes) {
const uniqueSizes = [...new Set(res.sizes)];
setSizes(uniqueSizes);
}
// if (res.brands) setBrands(res.brands);
}
} catch (error) {
console.log(error);
}
};
// console.log({ filteredProducts, filter, page, count, limit, total });
React.useMemo(() => {
let qParams = {
page: page,
limit: limit,
size: filter.size,
color: filter.color,
// brands: filter.brands,
price: filter.price.length ? `${filter.price[0]},${filter.price[1]}` : "",
};
if (slug) {
getProducts(slug, qParams);
}
}, [slug, page, limit, filter, count]);
React.useEffect(() => {
setPage(0);
}, [filter]);
const changeLimit = (limit) => {
setPage(0);
setLimit(limit);
};
const handleChange = (event, value) => {
// window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
setPage(value - 1);
};
const slugTitle = (slug) => slug.split("-").join(" ");
return (
<FadeTransition>
{/* {loading && (
<div className="section-big-py-space ratio_asos py-5">
<div className="custom-container">
<Skeleton type="ShopPage" />
</div>
</div>
)} */}
{!loading && products === null && (
<div className="section-big-py-space ratio_asos py-5">
<div className="custom-container">
<h3 style={{ color: "#32375A", textAlign: "center" }}>
Sorry, No Product Found!
</h3>
</div>
</div>
)}
{products && (
<div className="title-slug-section">
<h2 class="title-slug">{slug && slugTitle(slug)}</h2>
</div>
)}
{products && (
<section className="section-big-py-space ratio_asos">
{/* {type !== "category" && (
<div className="merchant-page-header">
<div className="custom-container">
<div
className="shadow-sm bg-white rounded p-3 mb-5 d-flex align-items-center w-100"
style={{ minHeight: "132px" }}
>
<div className="row align-items-center w-100">
<div className="col-lg-6">
<div className="row align-items-center">
{vandor && vandor.vendorImg && (
<div className="col-auto">
<Image
src={vandor.vendorImg}
alt={vandor.vendorName}
className="img-fluid merchant-img"
/>
</div>
)}
<div className="col-auto mt-lg-0 mt-2">
<h3 className="mb-0"> {vandor.vendorName} </h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)} */}
<div className="collection-wrapper">
<div className="custom-container">
<div className="row">
<div className="col-sm-3 collection-filter category-page-side">
{/* <SidebarFilter
type={type}
brands={brands}
colors={colors}
sizes={sizes}
onChange={(data) => setFilter(data)}
/> */}
<InnerCategory />
{products && (
<RowSlider title="New Products" products={products} />
)}
</div>
<div className="collection-content col-lg-9">
<div className="page-main-content">
<div className="collection-product-wrapper">
<div className="row">
<div className="col-xl-12">
{/* <Button
variant='contained'
className='bg-dark text-light d-lg-none mb-3 mt-2 w-100'
onClick={() => setSideFilter(true)}
>
<span className='filter-btn '>
<i
className='fa fa-filter'
aria-hidden='true'
></i>
Filter
</span>
</Button> */}
</div>
</div>
<MainFilter
type={type}
// brands={brands}
colors={colors}
sizes={sizes}
page={page}
limit={limit}
onCountChange={(c) => changeLimit(c)}
onChange={(data) => setFilter(data)}
/>
{/* <TopFilter
onCountChange={(x) => changeLimit(x)}
total={total}
page={page}
limit={limit}
setSideFilter={setSideFilter}
/> */}
{filteredProducts && (
<ProductList products={filteredProducts} />
)}
{count > 1 && (
<div className="d-flex justify-content-center mt-4">
<Pagination
count={count}
page={page + 1}
onChange={handleChange}
shape="rounded"
/>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
)}
{!loading && products?.length === 0 && (
<div className="merchant-page-header">
<div className="custom-container pt-5">
<div
className="shadow-sm bg-white rounded p-3 mb-5 d-flex align-items-center justify-content-center w-100"
style={{ minHeight: "132px" }}
>
<h3 className="mb-0">No Products found!</h3>
</div>
</div>
</div>
)}
<Drawer
open={sideFilter}
className="add-to-cart"
onClose={() => setSideFilter(false)}
transitionDuration={400}
style={{ paddingLeft: "15px" }}
>
<SidebarFilter
onClose={closeSideFilter}
type={type}
// brands={brands}
colors={colors}
sizes={sizes}
onChange={(data) => setFilter(data)}
/>
</Drawer>
</FadeTransition>
);
};
export default ShopPage;
Usually you would like to have URL to represent selected page, so you could refresh the page and still be on the same page. Or share the exact page via copy-paste of URL. Especially on e-commerce sites. So I would recommend to sync selected page with URL.
While it's so common scenario, i have couple hooks for that. First of all - URL hook, which should work with your reactjs app setup.
https://www.npmjs.com/package/hook-use-url
and then couple more hooks to not worry about pagination details inside component:
usePage.js:
import useUrl from "hook-use-url";
export default function usePage() {
const url = useUrl();
const page = url.get({ variable: "page" })
? parseInt(url.get({ variable: "page" }), 10)
: 1;
const setPage = (value) => {
url.multipleActions({
setPairs: [{ variable: "page", value: value }],
});
};
return [page, setPage];
}
and usePerPage.js:
import useUrl from "hook-use-url";
export default function usePerPage() {
const url = useUrl();
const perPage = url.get({ variable: "per-page" })
? parseInt(url.get({ variable: "per-page" }), 10)
: 25;
const setPerPage = (value) => {
url.multipleActions({
setPairs: [
{ variable: "page", value: 1 },
{ variable: "per-page", value },
],
});
};
return [perPage, setPerPage];
}
Inside components you can use these like so:
(Take a note that which page is 1st depends on your backend API, in my case 1st page is always 1 and not 0, but mui.com component starts from 0 that's why there is -1 and +1).
function MyComp(){
const [page, setPage] = usePage();
const [perPage, setPerPage] = usePerPage();
// ....
return (
<TablePagination
count={totalRows}
page={page - 1}
onPageChange={(e, newPage) => {
setPage(newPage + 1);
}}
rowsPerPage={perPage}
onRowsPerPageChange={(e) => {
setPerPage(e.target.value);
}}
/>
)
}

ReactJS: how using queryselector variable out a function

I'm using this code:
export default function Naviguation() {
const overBtnMenu = () => {
const navBars = document.querySelectorAll('.nav-bars')
for (let i = 0; i < navBars.length; i++) {
navBars[i].style.width = '40px'
}
}
const outBtnMenu = () => {
const navBars = document.querySelectorAll('.nav-bars')
navBars[0].style.width = '20px'
navBars[1].style.width = '30px'
navBars[2].style.width = '20px'
}
return (
<nav>
<img src={img} className='nav-logo' alt="logo" />
<div className='nav-btn-menu' onMouseEnter={overBtnMenu} onMouseLeave={outBtnMenu}>
<span className='nav-barTop nav-bars'></span>
<span className='nav-barMiddle nav-bars'></span>
<span className='nav-barBottom nav-bars'></span>
</div>
</nav>
)
}
the code runs correctly but I want to use the variable "navBars" once time like this
export default function Naviguation() {
const navBars = document.querySelectorAll('.nav-bars')
const overBtnMenu = () => {
for (let i = 0; i < navBars.length; i++) {
navBars[i].style.width = '40px'
}
}
const outBtnMenu = () => {
navBars[0].style.width = '20px'
navBars[1].style.width = '30px'
navBars[2].style.width = '20px'
}
return (
<nav>
<img src={img} className='nav-logo' alt="logo" />
<div className='nav-btn-menu' onMouseEnter={overBtnMenu} onMouseLeave={outBtnMenu}>
<span className='nav-barTop nav-bars'></span>
<span className='nav-barMiddle nav-bars'></span>
<span className='nav-barBottom nav-bars'></span>
</div>
</nav>
)
}
But the code doesn't run because it wrote on the console "navBars undefined..."
How using a variable out the function to using it into my function?
In React you have the concept of useRef whenever you need to get the value of an actual DOM element.
Rewrite your component as below:
import { useEffect, useRef } from 'react';
export default function Navigation() {
const navBarsRef = useRef(null);
const navBars = navBarsRef.current;
useEffect(() => {
navBarsRef.current = document.querySelectorAll('.nav-bars');
}, []);
const overBtnMenu = () => {
for (let i = 0; i < navBars.length; i++) {
navBars[i].style.width = '40px';
}
};
const outBtnMenu = () => {
navBars[0].style.width = '20px';
navBars[1].style.width = '30px';
navBars[2].style.width = '20px';
};
return (
<nav>
<img src={img} className="nav-logo" alt="logo" />
<div className="nav-btn-menu" onMouseEnter={overBtnMenu} onMouseLeave={outBtnMenu}>
<span className="nav-barTop nav-bars"></span>
<span className="nav-barMiddle nav-bars"></span>
<span className="nav-barBottom nav-bars"></span>
</div>
</nav>
);
}
I wrote your code:
import React, { useEffect, useRef } from 'react';
import img from "../../images/logo-sanimex.webp";
import "./Naviguation.css";
export default function Navigation() {
const navBarsRef = useRef(null);
const navBars = navBarsRef.current;
useEffect(() => {
navBarsRef.current = document.querySelectorAll('.nav-bars');
}, []);
const overBtnMenu = () => {
for (let i = 0; i < navBars.length; i++) {
navBars[i].style.width = '40px';
}
};
const outBtnMenu = () => {
navBars[0].style.width = '20px';
navBars[1].style.width = '30px';
navBars[2].style.width = '20px';
};
return (
<nav>
<img src={img} className="nav-logo" alt="logo" />
<div className="nav-btn-menu" onMouseEnter={overBtnMenu} onMouseLeave={outBtnMenu}>
<span className="nav-barTop nav-bars"></span>
<span className="nav-barMiddle nav-bars"></span>
<span className="nav-barBottom nav-bars"></span>
</div>
</nav>
);
}
But it's still not working :(
console.log shows:
Uncaught TypeError: navBars is null
overBtnMenu Naviguation.js:15
I found the solution !! I just remove "null" from const navBarsRef = useRef()
Thanks very much for helping
import "./Naviguation.css";
export default function Navigation() {
const navBarsRef = useRef();
const navBars = navBarsRef.current;
useEffect(() => {
navBarsRef.current = document.querySelectorAll('.nav-bars');
}, []);
const overBtnMenu = () => {
for (let i = 0; i < navBars.length; i++) {
navBars[i].style.width = '40px';
}
};
const outBtnMenu = () => {
navBars[0].style.width = '20px';
navBars[1].style.width = '30px';
navBars[2].style.width = '20px';
};
return (
<nav>
<img src={img} className="nav-logo" alt="logo" />
<div className="nav-btn-menu" onMouseEnter={overBtnMenu} onMouseLeave={outBtnMenu}>
<span className="nav-barTop nav-bars"></span>
<span className="nav-barMiddle nav-bars"></span>
<span className="nav-barBottom nav-bars"></span>
</div>
</nav>
);
}

useEffect code not firing dispatch even though it works without?

I've been troubleshooting and I narrowed it down to the useEffect.
This code as-is won't fire the dispatch(getOrderByIdAction(id)) as shown in the redux dev tools.
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { useParams, Link } from "react-router-dom";
import CheckoutSteps from '../components/CheckoutSteps'
import { getOrderByIdAction } from '../state/actions/orderActions'
import Message from '../components/Message'
const OrderConfirmationScreen = () => {
const dispatch = useDispatch()
const { id } = useParams()
// From Order Confirmation State
const orderDetails = useSelector((state) => state.orderConfirmation.orderDetails)
const shippingAddress = orderDetails.shippingAddress
const paymentMethod = orderDetails.paymentMethod
const orderItems = orderDetails.orderItems
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc + (cur.price * cur.qty)
}, 0)
const taxPrice = Number((itemPrice * .1).toFixed(2))
const totalPrice = taxPrice + itemPrice
useEffect(() => {
if (id) {
dispatch(getOrderByIdAction(id))
}
},[])
return (
<div className="main-container lg:p-16">
<CheckoutSteps step1 step2 step3 />
<div className="max-w-6xl mx-auto p-8 my-8 w-full">
<div className="flex flex-col lg:flex-row">
<div className="place-order-container bg-gray-50 rounded-md shadow-2xl m-2 p-6 lg:p-12 w-full">
<h1 className="text-center font-semibold text-xl mt-4 mb-6">Place Order</h1>
{/* { error && <Message message={ "Hello" }/>} */}
<div className="order-header-group grid grid-cols-2 bg-white rounded-lg shadow-xl pt-4 pb-8 px-2">
<div className="order-shipping-section my-2 mx-4 lg:p-2">
<h4 className="font-medium my-4">Shipping Address: </h4>
<div className="address-group">
<h6 className="text-sm">{shippingAddress.address}</h6>
<h6 className="text-sm">{shippingAddress.city}, {shippingAddress.zipcode}</h6>
<h6 className="text-sm">{shippingAddress.phone}</h6>
{ shippingAddress &&
<Link to={'/shipping'} className="underline my-2 block">Edit</Link> }
</div>
</div>
<div className="order-payment-section my-2 lg:p-2">
<h4 className="font-medium my-4">Payment Method: </h4>
{ paymentMethod === 'Paypal' && <i className="fab fa-paypal fa-lg mr-2" style={{color: "#253B80"}}></i>}
<span className="text-sm">{paymentMethod}</span>
</div>
</div>
<div className="order-items-group my-3 bg-white rounded-lg shadow-2xl p-2 lg:p-4">
<div className="order-items-group rounded-md p-6">
{ orderItems.map((item, index) =>
<div key={item.productId} className="grid grid-cols-8 gap-2 items-center my-4">
<img src={item.image} alt={item.name} className="col-span-2 w-40 p-4" />
<h6 className="col-span-3 text-sm">{item.name}</h6>
<h6 className="col-span-1 text-sm text-center">{item.qty}</h6>
<h6 className="col-span-1 text-sm text-center">x</h6>
<h6 className="col-span-1 text-sm text-center">{item.price}</h6>
<hr className="my-4 col-span-8"/>
</div>
)}
</div>
</div>
</div>
<div className="order-total-section bg-gray-50 rounded-md shadow-2xl m-2 p-6 lg:pb-8 w-full lg:w-2/5 h-1/2">
<h4 className="font-semibold text-xl text-center my-8">Order Summary</h4>
<div className="bg-white rounded-lg shadow-2xl p-4">
<div className="items-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Items: </span>
<span className="col-span-1 my-2 block text-sm">${itemPrice} </span>
</div>
<div className="shipping-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Shipping: </span>
<span className="col-span-1 my-2 block text-sm">FREE </span>
</div>
<div className="tax-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Tax: </span>
<span className="col-span-1 my-2 block text-sm">${taxPrice} </span>
</div>
<hr className="my-2" />
<div className="total-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm font-semibold">Total: </span>
<span className="col-span-1 my-2 block text-sm font-semibold">${totalPrice} </span>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default OrderConfirmationScreen;
If I just run dispatch without useEffect, it will fire off with a success and I get all the order info on my reducer. So I know it has something to do with the useEffect.
Unfortunately, then I get an error that says orderDetails is undefined because I need useEffect to run it before the component renders?
Not sure what I did wrong
EDIT:
instructor's orderScreen.js
const OrderScreen = ({ match, history }) => {
const orderId = match.params.id
const [sdkReady, setSdkReady] = useState(false)
const dispatch = useDispatch()
const orderDetails = useSelector((state) => state.orderDetails)
const { order, loading, error } = orderDetails
const orderPay = useSelector((state) => state.orderPay)
const { loading: loadingPay, success: successPay } = orderPay
const orderDeliver = useSelector((state) => state.orderDeliver)
const { loading: loadingDeliver, success: successDeliver } = orderDeliver
const userLogin = useSelector((state) => state.userLogin)
const { userInfo } = userLogin
if (!loading) {
// Calculate prices
const addDecimals = (num) => {
return (Math.round(num * 100) / 100).toFixed(2)
}
order.itemsPrice = addDecimals(
order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0)
)
}
useEffect(() => {
if (!userInfo) {
history.push('/login')
}
const addPayPalScript = async () => {
const { data: clientId } = await axios.get('/api/config/paypal')
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`
script.async = true
script.onload = () => {
setSdkReady(true)
}
document.body.appendChild(script)
}
if (!order || successPay || successDeliver || order._id !== orderId) {
dispatch({ type: ORDER_PAY_RESET })
dispatch({ type: ORDER_DELIVER_RESET })
dispatch(getOrderDetails(orderId))
} else if (!order.isPaid) {
if (!window.paypal) {
addPayPalScript()
} else {
setSdkReady(true)
}
}
}, [dispatch, orderId, successPay, successDeliver, order])
const successPaymentHandler = (paymentResult) => {
console.log(paymentResult)
dispatch(payOrder(orderId, paymentResult))
}
const deliverHandler = () => {
dispatch(deliverOrder(order))
}
return loading ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : ( jsx
edited code
OrderConfirmation.js
const OrderConfirmationScreen = () => {
const dispatch = useDispatch()
const { id } = useParams()
// From Order Confirmation State
const orderConfirmation = useSelector((state) => state.orderConfirmation)
const { loading, error, orderDetails } = orderConfirmation
useEffect(() => {
if(id) {
console.log(id);
dispatch(getOrderByIdAction(id))}
},[id])
const shippingAddress = orderDetails.shippingAddress
const paymentMethod = orderDetails.paymentMethod
const orderItems = orderDetails.orderItems
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc + (cur.price * cur.qty)
}, 0)
const taxPrice = Number((itemPrice * .1).toFixed(2))
const totalPrice = taxPrice + itemPrice
return loading? (<h3>loading..</h3>) :
( jsx
Since the data is not in the store when you render the component, you have to wait for the http request to complete.
You can return null when orderDetails is undefined, but consider adding a loading and error state to give a better user experience and make it easier for you to render a loading component or displaying an error
const OrderConfirmationScreen = () => {
const dispatch = useDispatch();
const { id } = useParams();
// From Order Confirmation State
const orderDetails = useSelector(
state => state.orderConfirmation.orderDetails,
);
useEffect(() => {
if (id) {
dispatch(getOrderByIdAction(id));
}
}, [id]);
// This is the important part.
// consider replacing this condition with loading and error from your state
if (!orderDetails) return <h3>Loading ...</h3>;
const { shippingAddress, paymentMethod, orderItems } = orderDetails;
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc + cur.price * cur.qty;
}, 0);
const taxPrice = Number((itemPrice * 0.1).toFixed(2));
const totalPrice = taxPrice + itemPrice;
return jsx...
};

Resources