with react DND only one element accepts the drag reference - reactjs

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.

Related

How to avoid closing DropDown on MouseOver?

I made Navigation component with dynamic menu items.
`
import React, { useState } from "react";
import NavMenuItems from "../data/NavMenuItems";
function NavBar() {
const [dropDown, setDropDown] = useState({});
const setDropDownOpen = (name) => {
setDropDown({[name]: true });
};
const setDropDownClose = (name) => {
setDropDown({[name]: false });
};
return (
<div className="flex flex-row my-2 mx-5">
{NavMenuItems.map((menu, index) => (
<>
<div key={menu.item} className="relative flex flex-col mx-1">
<div
className="bg-[#121C24] px-2 h-5 text-white text-sm hover:bg-green-700 hover:text-black hover:cursor-pointer "
onMouseEnter={() => setDropDownOpen(menu.item)}
onMouseLeave={() => setDropDownClose(menu.item)}
>
{menu.item}
</div>
{dropDown[menu.item] && (
<div className="bg-slate-200 absolute top-6 px-4 py-2"
onMouseEnter={() => setDropDownOpen(menu.item)}
onMouseLeave={() => setDropDownClose(menu.item)}
>
{menu.subitems.map((submenu, index) => (
<div key={index}>{submenu}</div>
))}
</div>
)}
</div>
</>
))}
</div>
);
}
export default NavBar;
NavMenuItems.js
`
const NavMenuItems = [
{
item: "Events",
subitems: ["Event1", "Event2", "Event2"],
},
{
item: "Reports",
subitems: ["Reports1", "Reports2", "Reports3"],
},
];
export default NavMenuItems
When i mouseover on tabs, its working fine. but when i move over dropdown sub menu, it closes and cant select anything in submenu items.
Can someone help with this?

How do i fix the error when updating state

In my application, I am making a request to the backend with redux-thunk and fetching the pricingData. I keep the items in the pricingData first 10 items in the items variable by passing certain operations. I want to update the questionRange variable with the first items[0] value. But using setQuestionRange immediately causes infinite loop.
So i tried these:
if i have an array with multiple elements, update the setQuestionRange. But but the error still persisted
React limits the number of renders to prevent an infinite loop.
if (items.length > 0) {
setQuestionRange(items[0])
}
I used useEffect. But this caused another error:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
useEffect(() => {
if (items) {
setQuestionRange(items[0])
}
}, [items])
How can i solve this? Here is my component:
import { Container } from '#/components/Container'
import Filters from './Filters'
import Cost from './Filters/Cost'
import { useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getAllPricing } from '#/store/actions/pricingAction'
function SwirlyDoodle({ className }) {
return (
<svg
aria-hidden="true"
viewBox="0 0 281 40"
className={className}
preserveAspectRatio="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M240.172 22.994c-8.007 1.246-15.477 2.23-31.26 4.114-18.506 2.21-26.323 2.977-34.487 3.386-2.971.149-3.727.324-6.566 1.523-15.124 6.388-43.775 9.404-69.425 7.31-26.207-2.14-50.986-7.103-78-15.624C10.912 20.7.988 16.143.734 14.657c-.066-.381.043-.344 1.324.456 10.423 6.506 49.649 16.322 77.8 19.468 23.708 2.65 38.249 2.95 55.821 1.156 9.407-.962 24.451-3.773 25.101-4.692.074-.104.053-.155-.058-.135-1.062.195-13.863-.271-18.848-.687-16.681-1.389-28.722-4.345-38.142-9.364-15.294-8.15-7.298-19.232 14.802-20.514 16.095-.934 32.793 1.517 47.423 6.96 13.524 5.033 17.942 12.326 11.463 18.922l-.859.874.697-.006c2.681-.026 15.304-1.302 29.208-2.953 25.845-3.07 35.659-4.519 54.027-7.978 9.863-1.858 11.021-2.048 13.055-2.145a61.901 61.901 0 0 0 4.506-.417c1.891-.259 2.151-.267 1.543-.047-.402.145-2.33.913-4.285 1.707-4.635 1.882-5.202 2.07-8.736 2.903-3.414.805-19.773 3.797-26.404 4.829Zm40.321-9.93c.1-.066.231-.085.29-.041.059.043-.024.096-.183.119-.177.024-.219-.007-.107-.079ZM172.299 26.22c9.364-6.058 5.161-12.039-12.304-17.51-11.656-3.653-23.145-5.47-35.243-5.576-22.552-.198-33.577 7.462-21.321 14.814 12.012 7.205 32.994 10.557 61.531 9.831 4.563-.116 5.372-.288 7.337-1.559Z"
/>
</svg>
)
}
export function Pricing() {
const [fieldSelectionMultipliers, setFieldSelectionMultipliers] = useState(0)
const [questionRange, setQuestionRange] = useState(0)
const [audienceCount, setAudienceCount] = useState(100)
const [totalCost, setTotalCost] = useState(null)
const dispatch = useDispatch()
const { pricingData } = useSelector((state) => state.pricing.getPricingData)
const isPricingDataEmpty = Object.keys(pricingData).length === 0
const fieldsData =
!isPricingDataEmpty &&
pricingData.items.filter((item) => item.content_object.content_type === 27)
const comboboxData =
!isPricingDataEmpty &&
pricingData.items.filter((data) => data.content_object.content_type === 41)
let items = !comboboxData
? []
: comboboxData.map((item) => ({
id: item.content_object.nanoid,
name: item.content_object.name,
multiplier: item.multiplier,
}))
const selectedFields = JSON.parse(JSON.stringify(fieldsData))
const exchangeRate = !isPricingDataEmpty && +pricingData.exchange_rate
const basePrice = !isPricingDataEmpty && +pricingData.base_price
const calculateCost = () => {
let numberFormat = Intl.NumberFormat('en-US')
let rate = exchangeRate * basePrice
let questionRangeMultiplier = questionRange ? questionRange.multiplier : 0
let total_multiplier =
(+fieldSelectionMultipliers + +questionRangeMultiplier) * rate
let total = Math.round(total_multiplier * audienceCount * 100) / 100
setTotalCost(numberFormat.format(total))
}
useEffect(() => {
calculateCost()
}, [fieldSelectionMultipliers, questionRange, exchangeRate, audienceCount])
useEffect(() => {
dispatch(getAllPricing())
}, [])
// if (items) {
// setQuestionRange(items[0])
// }
// useEffect(() => {
// if (items) {
// setQuestionRange(items[0])
// }
// }, [items])
return (
<section
id="pricing"
aria-label="Pricing"
className="bg-slate-900 py-20 sm:py-32"
>
<Container>
<div className="bg-slate-900">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl">
<span className="relative whitespace-nowrap">
<SwirlyDoodle className="absolute top-1/2 left-0 h-[1em] w-full fill-indigo-400" />
<span className="relative">Net fiyatlama,</span>
</span>{' '}
sometext
</h2>
<p className="my-5 text-lg text-slate-400">
sometext
</p>
</div>
</div>
<div className="relative">
<div className="absolute inset-0 h-1/2 bg-slate-900" />
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-lg overflow-hidden rounded-lg shadow-lg lg:flex lg:max-w-none">
<div className="flex-1 bg-white px-6 py-8 lg:p-12">
<h3 className="text-2xl font-bold text-gray-900 sm:text-3xl sm:tracking-tight">
sometext
</h3>
<div className="mt-8">
<div className="flex items-center">
<h4 className="flex-shrink-0 bg-white pr-4 text-base font-semibold text-indigo-600">
sometext
</h4>
<div className="flex-1 border-t-2 border-gray-200" />
</div>
{selectedFields && (
<Filters
selectedFields={selectedFields}
fieldSelectionMultipliers={fieldSelectionMultipliers}
setFieldSelectionMultipliers={
setFieldSelectionMultipliers
}
/>
)}
</div>
</div>
{comboboxData && (
<Cost
items={items}
questionRange={questionRange}
setQuestionRange={setQuestionRange}
audienceCount={audienceCount}
setAudienceCount={setAudienceCount}
totalCost={totalCost}
/>
)}
</div>
</div>
</div>
</div>
</Container>
</section>
)
}

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

Reactjs setInterval with scroll only trigger once

I have auto scroll function and scroll will start when user click the function as follow.
It is scroll 50px to y axis once when user click play button. But it is only scroll once even thought I have added interval. Interval is working because I saw the "scrolling" console.log is increasing. But scroll is not scrolling again.
May I know why scroll is not move again?
import React, { useState, useEffect, useRef } from "react";
import { useParams, NavLink } from "react-router-dom";
import { useQuery } from "#apollo/client";
import { getTrack, getTrackVariable } from "../../gql/track";
import ChordSheetJS from "chordsheetjs";
import {
YoutubeIcon,
FacebookIcon,
PlayIcon,
PauseIcon,
} from "../../assets/icons/svg_icons";
import { SettingIcon } from "../../assets/icons/svg_icons";
import paths from "../../routes/paths";
import { FacebookShareButton } from "react-share";
import GoTop from "../../components/go_top";
const TrackPage = () => {
const intervalId = useRef(null);
const { trackId } = useParams();
const [track, setTrack] = useState();
const [collapse, setCollapse] = useState(true);
const [play, setPlay] = useState(false);
const [speed, setSpeed] = useState(1);
const { loading, error, data } = useQuery(getTrack, {
variables: getTrackVariable(trackId),
});
const trackRef = useRef();
useEffect(() => {
if (!loading && !error) {
setTrack(data?.track);
}
}, [loading, error, data]);
const getChordSheet = (value) => {
const parser = new ChordSheetJS.ChordProParser();
const song = parser.parse(value);
const formatter = new ChordSheetJS.HtmlTableFormatter();
const chordSheet = formatter.format(song);
return chordSheet;
};
const handleError = (e) => {
e.target.onerror = null;
e.target.src = Monk;
};
const handleMenuCollapse = (e) => {
e.preventDefault();
setCollapse(!collapse);
};
const handleSpeedUp = () => {
setSpeed(speed + 1);
};
const handleSpeedDown = () => {
setSpeed(speed - 1);
};
const handleScroll = () => {
setPlay(!play);
if (play) {
console.log("stop");
clearInterval(intervalId.current);
} else {
let delayInMs = 100;
const onScrollStep = () => {
document.getElementById("content").scroll(0,50);
console.log("srolling")
};
intervalId.current = setInterval(onScrollStep, delayInMs);
console.log("play");
}
};
return (
<>
<div id="setting">
{/** the big div */}
<div
className={` w-36 h-56 bg-primary absolute top-[calc((100vh-384px)/2)] ${
collapse ? "hidden" : "right-0"
} " bg-primary rounded-b-lg items-center justify-center`}
>
<div>
<div className="items-center justify-center mt-5">
<div className="flex text-xs items-center justify-center ">
<span className=" text-sm text-white">Scroll</span>
</div>
<div className="flex text-xs pt-0 mt-0 items-center justify-center ">
<button
className="px-2 btn-sm flex w-20 items-center bg-transparent hover:bg-accent border text-white font-semibold hover:text-white border-white hover:border-transparent rounded "
onClick={handleScroll}
>
{play ? (
<PauseIcon className="text-white mr-2" />
) : (
<PlayIcon className="text-white mr-2" />
)}
{play ? <span>Pause</span> : <span>Play</span>}
</button>
</div>
<div className="flex text-xs items-center justify-center mt-2">
<button
className="w-auto bg-transparent mr-2 hover:bg-accent text-white font-semibold hover:text-white py-1 px-2 border border-white hover:border-transparent rounded"
onClick={handleSpeedDown}
>
-1
</button>
<button
className="w-auto bg-transparent ml-2 hover:bg-accent text-white font-semibold hover:text-white py-1 px-2 border border-white hover:border-transparent rounded"
onClick={handleSpeedUp}
>
+1
</button>
</div>
</div>
</div>
</div>
{/** the icon div */}
<div
className={`flex w-12 absolute top-[calc((100vh-384px)/2)] h-12 bg-primary
${collapse ? "animate-pulse right-0" : "right-36"}
cursor-pointer bg-primary rounded-l-lg items-center justify-center`}
onClick={handleMenuCollapse}
>
{/* <div className="w-5 h-5 bg-white rounded-full " /> */}
<SettingIcon />
</div>
</div>
<div id="track" ref={trackRef}>
<div className="flex flex-col w-full py-1 my-1 items-center bg-gray-50">
<div className="relative my-6 mx-auto md:min-w-[60%] max-h-full">
{track ? (
<div className="w-full">
<pre
className="px-5 textarea"
dangerouslySetInnerHTML={{
__html: getChordSheet(track.lyric),
}}
/>
</div>
) : (
<div></div>
)}
</div>
</div>
</div>
</>
);
};
export default TrackPage;
app.jsx
import React, { useState, useEffect } from "react";
import Header from "./components/header";
import SideMenu from "./components/side_menu";
import AppRoutes from "./routes";
import withUser from "./hocs/with_user";
import { isMobile } from "react-device-detect";
import { useLocation } from "react-router-dom";
import { AuthProvider, setAccessToken } from "./auth/auth_provider";
import { Toaster } from "react-hot-toast";
import AppContext from "./components/app_context";
import "./i18n";
import "./App.css";
function App(props) {
const [collapse, setCollapse] = useState(isMobile);
const [sideBarFull] = useState(true);
const location = useLocation();
const IsNormalPage = () => {
const blankPages = ["/login"];
for (let i = 0; i < blankPages.length; i++) {
if (location.pathname.startsWith(blankPages[i])) return
false;
}
return true;
};
useEffect(() => {
if (props.user) setAccessToken(props.user.t);
}, []);
const PageHeader = () => {
return (
<div className="h-[72px] w-full flex items-center align-middle justify-center bg-neutral shadow">
<div className="w-full text-center">
<Header />
</div>
</div>
);
};
return (
<AuthProvider user={props.user}>
<AppContext.Provider
value={{
collapse: collapse,
setCollapse: setCollapse,
}}
>
<div className="relative w-full min-h-screen h-full">
<div className="flex flex-row min-h-screen">
<div className="w-auto z-0 ">
<div className="flex-1 w-full max-h-screen mx-auto text-lg h-full shadow-lg bg-white overflow-y-auto">
{IsNormalPage() && <SideMenu showFullMenu={sideBarFull} />}
</div>
</div>
<div className="w-full max-h-screen flex flex-col z-10">
{IsNormalPage() && <PageHeader />}
<div id="content" className="flex-1 w-full max-h-screen mx-auto text-lg h-full shadow-lg bg-white overflow-y-auto">
<Toaster />
<AppRoutes />
</div>
</div>
</div>
</div>
</AppContext.Provider>
</AuthProvider>
);
}
export default withUser(App);
I think because you are toggling the play state in your component
setPlay(!play);
Are you trying to scroll to a specific div or just scroll for 50 px in the direction of y-axis? there are two approaches, you can use window or Refs.
an example using the refs to scroll to a specific node in the dom
const ScrollDemo = () => {
const myRef = useRef(null)
const executeScroll = () => { myRef.current.scrollIntoView()}
return (
<div>
<div ref={myRef}>Element to scroll to</div>
<button onClick={executeScroll}> Click to scroll </button>
<div/>
)
}
or if you just want to just scroll 50 pixel in the direction of y-axis
const scrollToTop = () => {
window.scrollTo(0,50);
};
return (
<button onClick={scrollToTop}>
Go down 50 px!
</button>
);
window.scrollTo is only working with html body. document.getElementById is only working overflow div.
useEffect(() => {
if (play) {
const onScrollStep = () => {
var e = document.getElementById("content");
if (e.scrollHeight - e.scrollTop === e.clientHeight) {
clearInterval(intervalId.current);
setPlay(!play);
return;
}
e.scroll(0, e.scrollTop + speed);
};
intervalId.current = setInterval(onScrollStep, delayInMs);
} else {
clearInterval(intervalId.current);
}
},[play, speed])

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