wavesurfer not displaying waveform - reactjs

I'm trying to use wavesurfer.js to display a waveform for audio files in a chat app i'm building using react . The audio plays fine but the wave is not displayed.
How do i display the waveform?
export const Wave = ({ url }) => {
const [isPlaying, setIsPlaying] = useState(false);
const waveformRef = useRef(null);
const trackRef = useRef(null);
useEffect(() => {
waveformRef.current = WaveSurfer.create({
container: "#waveform",
waveColor: "#FDCCE3",
progressColor: "#FA67AB",
backend: "MediaElement",
height: 80,
normalize: true,
responsive: true,
cursorColor: "transparent"
});
waveformRef.current.on("finish", () => {
setIsPlaying(false);
});
waveformRef.current.load(trackRef.current);
}, [url]);
const handlePlay = () => {
setIsPlaying(!isPlaying);
waveformRef.current.playPause();
};
return (
<div className="flex items-center h-24 bg-transparent gap-8">
<button
className="border-none outline-none cursor-pointer"
onClick={handlePlay}
>
<div className="bg-[#FA67AB] rounded-full p-2">
{!isPlaying ? (
<PlayArrowIcon htmlColor="white" />
) : (
<PauseIcon htmlColor="white" />
)}
</div>
</button>
<div ref={waveformRef} id="waveform"></div>
<audio id="track" src={url} ref={trackRef} />
</div>
);
};

Related

How to pass a value to my api page(backend) in Next.js so that it can be used by puppeteer

I am building an app that aggregates the main articles from a few game websites. I initially scrape the main page and get back the article titles and the associated href. These are then mapped so each article is displayed with the title and a button. I don't want to go to the website when I click on my articles. Instead, I would like the articles' body text to be displayed on my page by performing another scrape with the href connected to a specific article.
The "handler" button runs getArticleBody which connects to the api/puppeteer/rockpapershotgun.tsx.
The puppeteer logic in api/puppeteer/rockpapershotgun.tsx works – I get the text I want back from the page when I use the hardcoded test variable "href".
However I would like the string'href'to be dynamic. How do I pass the "a.url" from my index.js file where the "handler" button is into the api page so that puppeteer can use that value in the following line of code below?
await page.goto(`${href}`)
Here is my puppeteer logic in api/puppeteer/rockpapershotgun
import { url } from "node:inspector";
import { it } from "node:test";
import puppeteer, { MouseButton } from "puppeteer";
import { useState } from "react";
// TEST Variables - this is where I want to use a.url instead
const href =
"https://www.rockpapershotgun.com/forspokens-cinematic-trailer-shows-a-world-the-demo-suggests-it-might-not-live-up-to";
async function start() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// I want this href to be the "a.url" from my pages/index.js file
await page.goto(`${href}`);
const bodyText = await page.evaluate(() => {
return Array.from(document.querySelectorAll(".article_body_content")).map((x) => x.textContent);
});
console.log(bodyText);
await browser.close();
console.log("finished");
}
start();
export default start;
This is my pages/index.js
import Head from "next/head";
import Image from "next/image";
import { Inter } from "#next/font/google";
import styles from "../styles/Home.module.css";
import Article from "../components/ArticleComponent";
import { useState, useEffect, use } from "react";
import { slice } from "cheerio/lib/api/traversing";
import cheerio, { load } from "cheerio";
import { NextApiRequest, NextApiResponse } from "next/dist/shared/lib/utils";
import Link from "next/link";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
const [retrievedArticles, setRetrievedArticles] = useState<Object>([]);
const [finalArticles, setFinalArticles] = useState<Object>([]);
const [fetched, setFetched] = useState(false);
const [href, setHref] = useState("");
const [post, setPost] = useState("");
const [loading, setLoading] = useState(false);
const getUrl = async (url: string) => {
const res = await fetch(`/api/${url}`);
const { articles } = await res.json();
setRetrievedArticles(articles);
setFetched((fetched) => !fetched);
};
const urls: any[] = [];
const url = Array.isArray(retrievedArticles) ? retrievedArticles.forEach((a: any) => urls.push(a.url)) : [];
const handler = function (e: { target: any }) {
const href = e.target.getAttribute("data-href");
setHref(href);
};
const getArticleBody = async (url: string) => {
const res = await fetch(`/api/puppeteer/rockpapershotgun`);
};
useEffect(() => {
const toComponent =
Array.isArray(retrievedArticles) &&
retrievedArticles.map((a, i) => {
if (i <= 10) {
return (
<div className="" key={a.title.slice(5) + i}>
<div className="bg-orange-200 auto-cols-min h-70 justify-center items-center rounded-xl m-5">
<Article title={a.title} url={a.url} sitename={a.sitename} details={a.details} id={i} img={a.img} />
<Link
className="bg-orange-300 text-slate-800 text-xl p-3 rounded flex justify-center hover:bg-orange-400"
href={a.url}
>
Read
</Link>
<button
className="bg-orange-500 rounded flex p-4"
data-key={i}
data-href={a.url}
onClick={() => {
getArticleBody(a.url);
}}
>
Handler
</button>
</div>
</div>
);
}
if (i > 10) return;
});
setFinalArticles(toComponent);
}, [fetched, retrievedArticles]);
return (
<>
<div className="bg-slate-900 justify-center items-center p-20 flex flex-col">
<h1 className="sm:text-4xl md:text-6xl xl:text-6-xl text-4xl pb-10 text-slate-100">Gaming News</h1>
<div className=" flex-col flex gap-6 justify-center items-center pb-10">
<div className="grid grid-cols-1 sm:grid-cols-3 grid-rows-2 gap-4">
<button
onClick={() => {
getUrl("giantbomb");
}}
className="rounded-xl p-5 col-span-2 bg-red-400 hover:bg-slate-600"
>
GiantBomb
</button>
<button
onClick={() => {
getUrl("rockpapershotgun");
}}
className="bg-blue-400 col-span-2 sm:col-span-1 rounded-xl p-5 hover:bg-slate-600"
>
Rock Paper Shotgun
</button>
<button
onClick={() => {
getUrl("destructoid");
}}
className="rounded-xl p-5 col-span-2 sm:col-span-1 bg-slate-800 hover:bg-slate-600"
>
IGN(not)
</button>
<button
onClick={() => {
getUrl("destructoid");
}}
className="rounded-xl p-5 col-span-2 bg-green-400 hover:bg-slate-600"
>
Destructoid
</button>
<button
onClick={() => {
getUrl("escapist");
}}
className="rounded-xl p-5 col-span-2 bg-yellow-400 hover:bg-slate-600"
>
The Escapist
</button>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
<> {finalArticles}</>
</div>
</div>
</>
);
}
This is my ArticleComponent which mapped and display on my pages/index.js
import Link from "next/link";
import { ReactElement, JSXElementConstructor, ReactFragment, ReactPortal } from "react";
import { UrlObject } from "url";
import cheerio, { load } from "cheerio";
export default function ArticleComponent(props: {
url: string;
title: string;
sitename: string;
details: string;
id: number;
img: string;
}) {
const handleClick = async function (url: any) {
// const response = await fetch(`http://localhost:3000/api/article/${url}`);
console.log(url);
};
return (
<div className="flex justify-center items-center p-4 ">
<div className="cursor-pointer text-slate-50 pointer-cursor bg-slate-900 p-5 g-2 text-2sm flex-col flex justify-center items-center w-56 h-64 rounded-xl">
<h2 className="text-grey-100 ">{props.title}</h2>
{/* <h4>{props.id}</h4> */}
{/* <button onClick={() => handleClick(props.url)} className="bg-orange-400 p-2 rounded-xl">
Read
</button> */}
</div>
</div>
);
}

with react DND only one element accepts the drag reference

with react DND only one element accepts the drag reference i fetch data with firebase and put them in an array then map through the items and generate a card but only the last element accepts the drag ref
this is the place where I'm trying to implement it
import { useDrag } from "react-dnd";
const db = getFirestore(app);
export default function Projects({ currentUid }) {
const [projects, setProjects] = useState([]);
const [doneProjects, setDoneProjects] = useState([]);
const [tasks, setTasks] = useState([]);
const [dropDown, setDropDown] = useState(false);
const [key, setKey] = useState();
const [documentId, setDocumentId] = useState();
const [progressPercentage, setProgressPercentage] = useState(0);
const [{ isDragging }, dragRef] = useDrag(
() => ({
type: itemTypes.CARD,
item: { type: itemTypes.CARD },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
})
}),
[]
)
useEffect(() => {
// amay xwarawa bo aw projecta naya ka tawaw bwn
// const docRef3 = query(collection(db, "assignedProjects", currentUid, "projects"),where("isDone", "==", true));
const getProjects = async () => {
const docRef3 = query(
collection(db, "assignedProjects", currentUid, "projects"),
where("isDone", "==", true)
);
const docDoneSnap = await getDocs(docRef3);
onSnapshot(docRef3, (docDoneSnap) => {
const projectsDoneArr = [];
docDoneSnap.forEach((doc) => {
projectsDoneArr.push(doc.data());
});
setDoneProjects(projectsDoneArr);
});
const docRef = query(
collection(db, "assignedProjects", currentUid, "projects"),
where("isDone", "==", false)
);
// const docSnap = await getDocs(docRef);
// const projectsArr = [];
// docSnap.forEach((doc) => {
// projectsArr.push(
// {
// projects:doc.data(),
// projectId:doc.id
// }
// );
// documentId = doc.id;
// // console.log(doc.data());
// });
onSnapshot(docRef, (docSnap) => {
const projectsArr = [];
docSnap.forEach((doc) => {
projectsArr.push(
{
projects:doc.data(),
projectId:doc.id
}
);
setDocumentId(doc.id);
// console.log(doc.data());
});
setProjects(projectsArr);
});
console.log(documentId)
const taskRef = query(
collection(db, "assignedProjects", currentUid, "tasks"),
where("projectId", "==", documentId)
);
const taskSnap = await getDocs(taskRef);
const tasksArr = taskSnap.docs.map((doc) => doc.data());
setTasks(tasksArr);
};
getProjects();
}, []);
console.log(tasks)
useEffect(() => {
const getProgressPercentage = () => {
if (projects.length === 0) {
setProgressPercentage(100);
return;
}
const totalProjects = projects.length;
const totalDoneProjects = doneProjects.length;
const totalTasks = tasks.length;
const totalDoneTasks = tasks.filter((task) => task.isDone === true)
.length;
const totalProgress = totalDoneProjects + totalDoneTasks;
const total = totalProjects + totalTasks;
const percentage = Math.round((totalProgress / total) * 100);
setProgressPercentage(percentage);
};
getProgressPercentage();
}, [projects, doneProjects, tasks]);
function markProjectDone(project) {
const projectRef = collection(
db,
"assignedProjects",
currentUid,
"projects"
);
const docRef = doc(projectRef, project.projectId);
updateDoc(docRef, {
isDone: true,
});
}
const tasksToShow=tasks.map((task) => (
<div className={""}>
<p className="h-fit px-10 my-2 py-2 bg-slate-200 dark:bg-[#252525] rounded-2xl text-xl text-start">
{task.task1}
</p>
<p className="h-fit px-10 my-2 py-2 bg-slate-200 dark:bg-[#252525] rounded-2xl text-xl text-start">
{task.task2}
</p>
<p className="h-fit px-10 my-2 py-2 bg-slate-200 dark:bg-[#252525] rounded-2xl text-xl text-start">
{task.task3}
</p>
</div>
))
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.75, ease: "easeOut" }}
className="w-full min-h-full items-center font-Main text-[#121212] dark:text-[#ffffff] flex justify-between"
>
<div className="flex flex-col gap-20 h-full justify-around">
{/* <Progress value={50} label="Completed" className="bg-cyan-500" /> */}
{/* {isNaN(progressPercentage)?"Loading ...":<div className='h-5 w-full bg-gray-300'>
<div
style={{ width: `${progressPercentage}%`}}
className={`h-full ${
progressPercentage < 70 ? 'bg-red-600' : 'bg-green-600'}`}>
{progressPercentage}%
</div>
</div>} */}
<h1 className="text-6xl">Projects in progress</h1>
{projects.map((project) => (
<div ref={dragRef} key={project.id} className=" text-[#121212] dark:text-[#ffffff] flex flex-col gap-2" draggable={true} >
<div className="bg-slate-200 dark:bg-[#252525] w-[35rem] items-center h-32 rounded-2xl py-2 flex justify-between px-10" >
<div className="flex flex-col items-start justify-start gap-2" >
<h1 className="text-4xl text-[#018786] dark:text-[#03dac6] ">
{project.projects.title}
</h1>
<p className="text-lg text-[#12121247] dark:text-[#ffffff70]">
Due date :{" "}
<span className="text-[#121212c5] dark:text-[#ffffffb5] ">
{project.projects.dueDate}
</span>
</p>
</div>
<div className="text-5xl">
<button
onClick={() => {
setKey(project.title);
setDropDown(dropDown === false ? true : false);
}}
className="mx-5 dark:text-[#bb86fc] text-[#6200ee]"
>
{dropDown && key === project.title ? (
<ion-icon name="chevron-up-circle-outline"></ion-icon>
) : (
<ion-icon name="chevron-down-circle-outline"></ion-icon>
)}
</button>
<button className="text-[#018786] dark:text-[#03dac6]" onClick={()=>{
markProjectDone(project)
}}>
<ion-icon name="checkmark-circle-outline"></ion-icon>
</button>
</div>
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.75, ease: "easeOut" }}
className={`h-fit transition-all ${
dropDown && key === project.title ? "" : "hidden"
} w-full py-2`}
>
<div
className={`h-fit px-10 py-2 dark:bg-[#3700b3] bg-[#6200ee] rounded-2xl w-full flex flex-col justify-start`}
>
<div className="h-fit w-full ">
<p className="text-xl flex text-[#ffffffc1]">
Description :{" "}
<span className=" text-[#ffffff] ">
<p>{project.projects.description}</p>
</span>
</p>
</div>
<p>{project.projects.status}</p>
</div>
{tasksToShow}
</motion.div>
</div>
))}
</div>
<div className="flex flex-col gap-20 h-full justify-around">
<h1 className="text-6xl">Done Projects</h1>
{doneProjects.map((project) => (
<div className="">
<div className=" bg-[#0187878e] dark:bg-[#03dac579] w-[35rem] items-center h-32 rounded-2xl py-2 flex justify-between px-10">
<div className="flex flex-col items-start justify-start gap-2">
<h1 className="text-4xl text-[#121212]">{project.title}</h1>
<p className="text-lg text-[#12121247] ">
Due date :{" "}
<span className="text-[#121212c5] ">{project.dueDate}</span>
</p>
</div>
</div>
</div>
))}
</div>
</motion.div>
);
}
and this is the index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import {DndProvider} from "react-dnd";
import {HTML5Backend} from "react-dnd-html5-backend";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
</BrowserRouter>
</React.StrictMode>
);
I tried putting the reference in different divs but still the same.

Passing setState from useState hook as a prop in a firebase function or any other component

Currently not facing any problems I'm just wondering what the best practices for passing setState to a function or component, specifically a firebase function that is declared on another file or a form that is designed to be reusable.
This is a function is from my firebase file
`
export function getResumes({ applicationtState: applicationState }) {
onSnapshot(
query(collection(db, 'applications'), orderBy('timestamp', 'desc')),
(querySnapshot) => {
const arrays = []
querySnapshot.forEach((snap) => {
arrays.push(snap.data())
})
applicationState(arrays)
}
)
}
`
this is how i call the function on my resumes file
`
const [application, setApplication] = useState([])
useEffect(() => {
getResumes({ applicationtState: setApplication })
}, [])
`
In another instance I have a form component which whis i am calling on multiple pages
`
function EducationBox({
highSchoolState,
highSchoolValue,
highSchoolCourseOfStudyState,
highSchoolCourseOfStudyValue,
highSchoolGraduateState,
highSchoolNumberOfYearsCompletedState,
highSchoolNumberOfYearsCompletedValue,
highSchoolHonorsReceivedState,
highSchoolHonorsReceivedValue,
collegeState,
collegeValue,
collegeCourseOfStudyState,
collegeCourseOfStudyValue,
collegeGraduateState,
// collegeGraduateValue,
collegeNumberOfYearsCompletedState,
collegeNumberOfYearsCompletedValue,
collegeHonorsReceivedState,
collegeHonorsReceivedValue,
GradState,
GradValue,
GradCourseOfStudyState,
GradCourseOfStudyValue,
GradGraduateState,
// GradGraduateValue,
GradNumberOfYearsCompletedState,
GradNumberOfYearsCompletedValue,
GradHonorsReceivedState,
GradHonorsReceivedValue,
tradeState,
tradeValue,
tradeCourseOfStudyState,
tradeCourseOfStudyValue,
tradeGraduateState,
// tradeGraduateValue,
tradeNumberOfYearsCompletedState,
tradeNumberOfYearsCompletedValue,
tradeHonorsReceivedState,
tradeHonorsReceivedValue,
}) {
return (
<div className=" flex w-full grid-rows-4 flex-col rounded-[20px] outline outline-2 outline-[#b5b5b5]">
<SchoolItem
SchoolState={highSchoolState}
SchoolValue={highSchoolValue}
CourseOfStudyState={highSchoolCourseOfStudyState}
SchoolCourseOfStudyValue={highSchoolCourseOfStudyValue}
SchoolGraduateState={highSchoolGraduateState}
title={'High School'}
NumberOfYearsCompletedState={highSchoolNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={highSchoolNumberOfYearsCompletedValue}
HonorsReceivedState={highSchoolHonorsReceivedState}
HonorsReceivedValue={highSchoolHonorsReceivedValue}
/>
<SchoolItem
SchoolState={collegeState}
SchoolValue={collegeValue}
CourseOfStudyState={collegeCourseOfStudyState}
SchoolCourseOfStudyValue={collegeCourseOfStudyValue}
SchoolGraduateState={collegeGraduateState}
title={'College'}
NumberOfYearsCompletedState={collegeNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={collegeNumberOfYearsCompletedValue}
HonorsReceivedState={collegeHonorsReceivedState}
HonorsReceivedValue={collegeHonorsReceivedValue}
/>
<SchoolItem
SchoolState={GradState}
SchoolValue={GradValue}
CourseOfStudyState={GradCourseOfStudyState}
SchoolCourseOfStudyValue={GradCourseOfStudyValue}
SchoolGraduateState={GradGraduateState}
title={'Grad School'}
NumberOfYearsCompletedState={GradNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={GradNumberOfYearsCompletedValue}
HonorsReceivedState={GradHonorsReceivedState}
HonorsReceivedValue={GradHonorsReceivedValue}
/>
<SchoolItem
SchoolState={tradeState}
SchoolValue={tradeValue}
CourseOfStudyState={tradeCourseOfStudyState}
SchoolCourseOfStudyValue={tradeCourseOfStudyValue}
SchoolGraduateState={tradeGraduateState}
title={'Trade School'}
NumberOfYearsCompletedState={tradeNumberOfYearsCompletedState}
NumberOfYearsCompletedValue={tradeNumberOfYearsCompletedValue}
HonorsReceivedState={tradeHonorsReceivedState}
HonorsReceivedValue={tradeHonorsReceivedValue}
/>
</div>
)
}
function SchoolItem({
SchoolState,
SchoolValue,
CourseOfStudyState,
SchoolCourseOfStudyValue,
SchoolGraduateState,
title,
NumberOfYearsCompletedState,
NumberOfYearsCompletedValue,
HonorsReceivedState,
HonorsReceivedValue,
}) {
return (
<div className=" flex w-full grid-cols-6 flex-col items-center justify-center px-10 text-center md:flex-row">
<h4 className=" flex text-lg font-bold"> {title}</h4>
<div className=" mx-3 w-full">
<TextInput
value={SchoolValue}
widthPercentage="w-full"
placeHolder="School Name"
onChange={(text) => {
SchoolState(text.target.value)
}}
/>
</div>
<div className=" mx-3 w-full">
<TextInput
widthPercentage="w-full"
placeHolder="Course Of Study"
onChange={(text) => {
CourseOfStudyState(text.target.value)
}}
value={SchoolCourseOfStudyValue}
/>
</div>
<div className=" mx-3 w-full">
<h5>Graduated?</h5>
<RadioButton answerState={SchoolGraduateState} />
</div>
<div className=" mx-3 w-full">
<TextInput
value={HonorsReceivedValue}
widthPercentage="w-full"
placeHolder="Honors Received"
onChange={(text) => {
HonorsReceivedState(text.target.value)
}}
/>
</div>
<div className=" mx-3 w-full">
<TextInput
value={NumberOfYearsCompletedValue}
widthPercentage="w-full"
placeHolder="# of years Completed"
onChange={(text) => {
NumberOfYearsCompletedState(text.target.value)
}}
/>
</div>
</div>
)
}
export default EducationBox
`
I just want to know what best practices is. Please Help!
you can take help of promises and use something like this. and this way your function could become more reusable could not be dependent on any args or state :-
export function getResumes() {
return new Promise((resolve, reject) => {
onSnapshot(
query(collection(db, 'applications'), orderBy('timestamp', 'desc')),
(querySnapshot) => {
const arrays = []
querySnapshot.forEach((snap) => {
arrays.push(snap.data())
})
resolve(arrays)
}
)
})
}
const [application, setApplication] = useState([])
useEffect(() => {
getData()
}, [])
const getData = async () => {
try {
const resumes = await getResumes()
setApplication(resumes)
} catch (err) {
console.log("error", err);
}
}

Refetch query and cache evict on route change

I have a problem regarding my GraphQL query cache. I'm trying to create pagination for this query. I have some music playlists and every playlist have its own page. On a playlist page I call a query to get the playlist songs (which I get as a JSON response). This is the merge function from my InMemoryCache:
getMoreSpotifyPlaylists: {
keyArgs: [],
merge(existing: PaginatedJsonResponse | undefined, incoming: PaginatedJsonResponse): PaginatedJsonResponse {
var existingJSON, incomingJSON
if (existing?.['jsonData']) {
existingJSON = JSON.parse(existing?.['jsonData'])
} else {
return incoming
}
incomingJSON = JSON.parse(incoming?.['jsonData'])
var finalJSON = {
...incomingJSON,
items: existingJSON?.items.concat(incomingJSON?.items)
}
return {
...incoming,
jsonData: JSON.stringify(finalJSON)
}
}
}
The problem is that when I change the playlist page, the first songs remain the songs from the other playlist. I've tried to evict the cache on page change but this didn't worked.
This is the playlist page:
const SpotifyPlaylistPage: NextPage = () => {
const apolloClient = useApolloClient();
const router = useRouter()
const [rerender, setRerender] = useState(false)
const [progress, setProgress] = useState(100)
var id = typeof router.query.id === "string" ? router.query.id : ""
const { t: menuTranslation } = useTranslation('menu')
const { t: commonTranslation } = useTranslation('common')
const { t: actionsTranslation } = useTranslation('actions')
const { data: playlistData, loading: playlistDataLoading } = useGetSpotifyPlaylistQuery({ variables: { id } })
const { data: spotifyPlaylistSongsData, loading: spotifyPlaylistSongsLoading, fetchMore: fetchMoreSpotifyPlaylistSongs, variables: spotifyPlaylistSongsVariables } = useGetSpotifyPlaylistSongsQuery({ variables: { id, offset: 0, limit: 5 } })
const [spotifyPlaylistSongsJSON, setSpotifyPlaylistSongsJSON] = useState(null)
const [newOffset, setNewOffset] = useState(5)
const [playlistJSON, setPlaylistJSON] = useState(null)
const [isPublicIconFilled, setIsPublicIconFilled] = useState(false)
const [isCollaborativeIconFilled, setIsCollaborativeIconFilled] = useState(false)
useEffect(() => {
const cache = new InMemoryCache()
if (cache.evict({ fieldName: "getMoreSpotifyPlaylists", broadcast: false })) {
console.log('evicted')
} else {
console.log("error")
}
setRerender(r => !r)
}, [router.query.id, id]);
useEffect(() => {
if (playlistData?.getSpotifyPlaylist?.jsonData) {
setPlaylistJSON(JSON.parse(playlistData?.getSpotifyPlaylist?.jsonData))
}
if (spotifyPlaylistSongsData?.getSpotifyPlaylistSongs?.jsonData) {
setSpotifyPlaylistSongsJSON(JSON.parse(spotifyPlaylistSongsData?.getSpotifyPlaylistSongs?.jsonData))
}
}, [apolloClient, playlistData?.getSpotifyPlaylist?.jsonData, playlistDataLoading, spotifyPlaylistSongsData?.getSpotifyPlaylistSongs?.jsonData])
var showContent = useRef(false)
if (!playlistDataLoading) {
if (id && playlistData?.getSpotifyPlaylist?.jsonData) {
showContent.current = true
} else {
router.push("/")
}
}
if (showContent.current) {
return (
<AppContainer routeType='public' key={id}>
<LaHead title={menuTranslation('home')} />
<div className='w-full h-96 top-0 left-0 absolute z-0'>
<Image alt="test" layout='fill' objectFit='cover' src={playlistJSON?.images[0]?.url} />
<div className='w-full h-full top-0 left-0 absolute bg-gradient-to-t from-la-mauve1 dark:from-la-martinique3 to-transparent'></div>
<div className='w-full h-full top-0 left-0 absolute bg-la-mauve1/80 dark:bg-la-martinique3/80' />
</div>
<div className='z-10'>
<NavbarWithSearchBar />
<Container size='large'>
<LaBreadcrumb breadcrumbElements={[{
href: "/",
name: "Home",
isActive: false
}, {
href: "#",
name: playlistJSON?.name,
isActive: true
}]} />
<div className='flex items-center justify-between'>
<div className='flex items-center space-x-2 md:space-x-4'>
<Title>{playlistJSON?.name}</Title>
{playlistJSON?.public ?
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('public')} onMouseEnter={() => { setIsPublicIconFilled(true) }} onMouseLeave={() => { setIsPublicIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<UsersGroupIcon fillEffect={isPublicIconFilled} className='w-6 h-6' />
</span>
:
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('private')} onMouseEnter={() => { setIsPublicIconFilled(true) }} onMouseLeave={() => { setIsPublicIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<LockIcon fillEffect={isPublicIconFilled} className='w-6 h-6' />
</span>}
{playlistJSON?.collaborative ?
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('collaborative')} onMouseEnter={() => { setIsCollaborativeIconFilled(true) }} onMouseLeave={() => { setIsCollaborativeIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<UsersIcon fillEffect={isCollaborativeIconFilled} className='w-6 h-6' />
</span>
:
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('individual')} onMouseEnter={() => { setIsCollaborativeIconFilled(true) }} onMouseLeave={() => { setIsCollaborativeIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<UserIcon2 fillEffect={isCollaborativeIconFilled} className='w-6 h-6' />
</span>}
</div>
<Button onClick={async () => {
}} fullWidth={false} content={<RefreshIcon className='w-6 h-6' />} />
</div>
<div className='text-flex italic'>
{playlistJSON?.description}
</div>
{spotifyPlaylistSongsLoading ? <div className='flex w-full justify-center'>
<LaLoader />
</div> : spotifyPlaylistSongsJSON?.items.length > 0 ?
<>
<ThreeBoxesContainer>
{spotifyPlaylistSongsJSON?.items?.map((item: any) => <SongBox key={item.track.id} songData={item} />)}
{spotifyPlaylistSongsData && spotifyPlaylistSongsData.getSpotifyPlaylistSongs.hasMore ?
<Button onClick={async () => {
await fetchMoreSpotifyPlaylistSongs({ variables: { id, offset: newOffset, limit: spotifyPlaylistSongsVariables?.limit } })
setNewOffset(newOffset + spotifyPlaylistSongsVariables.limit)
}} content={commonTranslation('showmore')} /> : null}
</ThreeBoxesContainer>
</>
: <MessageBox type='warning' title={commonTranslation('error')} message={actionsTranslation('noPlaylists')} />}
</Container>
<BottomNavbar />
</div>
</AppContainer>
);
}
return (
<div><LoadingBar className="!bg-la-seance2 !dark:bg-la-seance1" progress={progress}
onLoaderFinished={() => setProgress(0)} /></div>
)
}

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

Resources