|React:useOutsideClick hook gives forwardRef warning message - javascript

From firebase I fetch data and map this data to be shown in cards, every card has edit component and CardComponent(edit component parent) which use ref provided from useHandleOpen custom hook
Error message:
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Component where ref is in use
export default function EditCard(id) {
const { ref, isOpen, setIsOpen } = useHandleOpen(false);
return (
<div>
<GoKebabVertical
ref={ref}
className="cursor-pointer "
onClick={() => setIsOpen(true)}
/>
{isOpen && (
<div
ref={ref}
className="w-20 h-15 bg-white z-30 rounded-md absolute text-center
top-0 right-0
"
>
<p className="hover:bg-blue-300 hover:text-white cursor-pointer">
Edytuj
</p>
<p
className="bg-red-400 text-white font-semibold hover:bg-red-600 cursor-pointer"
onClick={() => {}}
>
Usuń
</p>
</div>
)}
</div>
);
}
export const Edit = memo(EditCard);
Card component which use ref tho and its Edit parent
const Card = ({ data, id }) => {
const editRef = useRef(null);
const { ref, isOpen, setIsOpen } = useHandleOpen(editRef);
return (
<div
className="bg-gradient-to-r from-blue-200 to-purple-500
w-64 h-28 rounded-md relative
"
>
<div className="relative top-0 left-0">
<p className="px-2 pt-1 font-bold text-gray-800 text-lg">
{data.subject_name}
</p>
<p className="px-2 text-xs text-gray-800">Sala 101</p>
</div>
<div
className="absolute top-0 right-0 rounded-r-md
rounded-b-none
bg-white w-6 h-7
grid place-items-center
"
>
<Edit id={id} />
</div>
<div className="absolute bottom-9 left-0 mb-2.5">
<p className="px-2 text-xs text-gray-800">{data.teacher}</p>
</div>
<div className=" flex direction-row mt-7 text-center w-full justify-end align-bottom pr-2 ">
<div className="rounded-lg w-14 h-7 mx-3 bg-purple-300">
<a
href={data.link_subject}
className="text-gray-800
"
target="_blank"
rel="noreferrer"
>
Lekcja
</a>
</div>
<div className="rounded-lg w-14 h-7 bg-gray-800 ">
<a
href={data.link_online_lesson}
target="_blank"
rel="noreferrer"
className="text-white"
>
Online
</a>
</div>
</div>
<div
className=" absolute bottom-0 left-0 rounded-l-md
bg-white w-7 h-6 grid place-items-center devide-solid"
>
{isOpen ? (
<AiOutlineUp
className="cursor-pointer"
ref={ref}
onClick={() => setIsOpen(true)}
/>
) : (
<AiOutlineDown
className="cursor-pointer"
ref={ref}
onClick={() => setIsOpen(true)}
/>
)}
</div>
{isOpen && (
<div
className="bg-gradient-to-r from-blue-200 to-purple-500 w-full text-left rounded-b-md p-4 "
ref={ref}
>
<p className="font-bold text-gray-800 text-sm ">Lekcje w:</p>
<p className="text-gray-800 text-sm">PN: 8-12</p>
</div>
)}
</div>
);
};
export const CardSubject = memo(Card);
Custom hook with ref:
export default function useHandleOpen() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(!isOpen);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, !isOpen);
return () => {
document.removeEventListener("click", handleClickOutside, !isOpen);
};
});
return { ref, isOpen, setIsOpen };
}
Edit: Tried change it this way, but this displays warning too.
export default function useHandleOpen(ref) {
const [isOpen, setIsOpen] = useState(false);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(!isOpen);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, !isOpen);
return () => {
document.removeEventListener("click", handleClickOutside, !isOpen);
};
});
return { ref, isOpen, setIsOpen };
}
And use hook like this:
const editRef = useRef(null);
const { ref, isOpen, setIsOpen } = useHandleOpen(editRef);

Ok i fixed it by removing ref from places where i use onClick method for changing the state

Related

React: DOMException: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local answer sdp: Called in wrong state: stable [duplicate]

This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
Closed 3 months ago.
I am making a live stream application using peerjs in React with vite.
I know there are already many questions like this but this is specific to ReactJs. I could not find any solution related to React.
I get this error:
React: DOMException: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local answer sdp: Called in wrong state: stable
Edit: I found the answer and in short this is a problem that uses two other problems' solutions to fix it.
This is my webRTC.ts file.
import Peer, { DataConnection, MediaConnection } from "peerjs";
import socket from "./socket";
import { JOIN_LECTURE } from "./socketaction";
export const getUserStream: () => Promise<MediaStream> =
(): Promise<MediaStream> => {
return navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
};
export const getScreenStream: () => Promise<MediaStream> =
(): Promise<MediaStream> => {
return navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
};
export const initPeer: (peer: Peer, name: string) => void = (
peer: Peer,
name: string
) => {
// On Error
peer.on("error", (err: Error): void => {
console.error(err);
});
// On Open
peer.on("open", (id: string): void => {
console.log("[p]open", name);
console.log(id);
});
// On Connection
peer.addListener("connection", (conn: DataConnection): void => {
conn.on("data", (data: unknown): void => {
console.log(data, "data");
});
});
};
export const answerCall = (
peer: Peer,
lectureId: string,
cb: (stream: MediaStream, type: "user" | "screen") => void
): void => {
peer.addListener("open", () => {
socket.emit(JOIN_LECTURE, {
peerId: peer.id,
lectureId: lectureId,
});
// socket.emit(GET_STREAM);
});
peer.on("call", (call: MediaConnection): void => {
call.answer();
call.on("stream", (stream: MediaStream): void => {
cb(stream, call.metadata.streamType);
});
call.on("error", console.error);
call.on("close", (): void => {
console.log("call closed");
});
});
};
export const shareUser = (
peer: Peer,
stream: MediaStream,
studentId: string
) => {
if (peer && studentId) {
console.log(studentId);
const conn = peer.connect(studentId);
conn.on("open", () => {
const call: MediaConnection = peer.call(studentId, stream, {
metadata: {
streamType: "user",
},
});
call.on("error", console.error);
call.on("close", (): void => {
console.log("call closed");
});
});
conn.on("data", console.log);
conn.on("error", console.error);
conn.on("iceStateChanged", () => console.log("IceStageChanged"));
}
};
This is my main.tsx file
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { ThemeProvider } from "#mui/system";
import MuiTheme from "./mui-theme";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { CookiesProvider } from "react-cookie";
import { StrictMode } from "react";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<Provider store={store}>
<CookiesProvider>
<ThemeProvider theme={MuiTheme}>
<App />
</ThemeProvider>
<ToastContainer theme="dark" position="bottom-right" />
</CookiesProvider>
</Provider>
</StrictMode>
);
This is my liveLecture.tsx file.
import {
CallEndRounded,
ChatRounded,
DoNotTouchRounded,
FullscreenRounded,
MicOffRounded,
MicRounded,
PanToolRounded,
PausePresentationRounded,
PresentToAllRounded,
SendRounded,
SpeakerNotesOffRounded,
VideocamOffRounded,
VideocamRounded,
ViewComfyOutlined,
ViewComfyRounded,
} from "#mui/icons-material";
import { Avatar, IconButton, Input, InputAdornment } from "#mui/material";
import { AnyAction, Dispatch } from "#reduxjs/toolkit";
import Peer, { DataConnection, MediaConnection } from "peerjs";
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
import Logo from "../components/logo";
import VideoPlayer from "../components/videoplayer";
import { User } from "../redux/slices/user";
import { RootState } from "../redux/store";
import socket from "../utils/socket";
import { GET_STREAM, IS_ADMIN, JOIN_LECTURE } from "../utils/socketaction";
import {
answerCall,
getScreenStream,
getUserStream,
initPeer,
shareUser,
} from "../utils/webRTC";
interface Paused {
video: boolean;
audio: boolean;
}
function LiveLecture() {
const { lectureId } = useParams();
const user: User = useSelector((state: RootState): User => state.user);
const [userStream, setUserStream] = useState<MediaStream | undefined>();
const [screenStream, setScreenStream] = useState<MediaStream | undefined>();
const [isHandRaised, setIsHandRaised] = useState<boolean>(false);
const [isChatOpen, setIsChatOpen] = useState<boolean>(true);
const [isPresentationView, setIsPresentationView] = useState<boolean>(true);
const [paused, setPaused] = useState<Paused>({
audio: true,
video: true,
});
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [userPeer, setUserPeer] = useState<Peer>(new Peer());
const [screenPeer, setScreenPeer] = useState<Peer>(new Peer());
const [isFullScreen, setIsFullScreen] = useState(false);
const dispatch: Dispatch<AnyAction> = useDispatch();
const shareUserStream = (studentId: string) => {
if (userStream) {
shareUser(userPeer, userStream, studentId);
} else {
getUserStream().then((stream: MediaStream): void => {
setUserStream(stream);
console.log(userPeer, studentId);
shareUser(userPeer, stream, studentId);
});
}
};
useEffect((): (() => void) => {
if (userPeer && lectureId) {
initPeer(userPeer, "user");
answerCall(
userPeer,
lectureId,
(stream: MediaStream, type: "user" | "screen"): void => {
console.log("second");
if (type == "user") {
setUserStream(stream);
} else {
setScreenStream(stream);
}
}
);
}
return (): void => {};
}, [userPeer]);
useEffect((): (() => void) => {
if (screenPeer) {
initPeer(screenPeer, "screen");
}
return (): void => {};
}, [screenPeer]);
useEffect((): (() => void) => {
socket.on(IS_ADMIN, (admin: boolean) => {
setIsAdmin(admin);
if (admin) {
socket.on(GET_STREAM, (studentPeerId) => {
// call the user
studentPeerId && shareUserStream(studentPeerId);
});
}
console.log(admin);
});
return (): void => {};
}, []);
return (
<div className="flex overflow-hidden flex-col w-full h-screen">
<div className="w-full flex px-10 bg-gray-900 shadow-lg py-3">
<div className=" flex sm:gap-6 gap-4 divide-x-2 justify-center items-center text-2xl font-semibold text-white">
<div className="hidden sm:block">
<Logo />
</div>
<div className="md:pl-6 pl-4">Batch Name</div>
<div className="select-none ring-2 ring-red-500 bg-white text-red-500 font-bold uppercase px-2 rounded-lg">
Live
</div>
</div>
</div>
<div className="flex py-4 lg:justify-around lg:flex-row bg-secondary-100 grow h-[calc(100vh-16rem)] flex-col">
<div className="flex grow gap-3 flex-col justify-between items-center">
<div
className={`flex justify-center items-center grow px-4 lg:w-full`}
>
<div
className={`grid ${
!isChatOpen && "px-10"
} w-full gap-4 grid-cols-4 grid-row-4`}
>
<div
className={`${
isChatOpen ? "col-span-1" : "col-span-3 sm:col-span-1"
}`}
>
{/* secondary player */}
<VideoPlayer
stream={isPresentationView ? userStream : screenStream}
/>
</div>
<div
className={`col-span-3 px-2 grow flex justify-center items-center`}
>
{/* primary player */}
<VideoPlayer
isFullScreen={isFullScreen}
stream={isPresentationView ? screenStream : userStream}
/>
</div>
</div>
</div>
<div className="justify-center items-center">
<div className="py-2 px-6 rounded-full bg-gray-900 text-gray-500 flex gap-2 sm:gap-6 justify-center items-center">
{isAdmin ? (
<>
<IconButton
onClick={(): void => {
setPaused(
(pp: Paused): Paused => ({
...pp,
audio: !pp.audio,
})
);
}}
color="inherit"
>
{paused.audio ? (
<MicOffRounded color="inherit" />
) : (
<MicRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={(): void => {
setPaused(
(pp: Paused): Paused => ({
...pp,
video: !pp.video,
})
);
}}
color="inherit"
>
{paused.video ? (
<VideocamOffRounded color="inherit" />
) : (
<VideocamRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={() => {
if (!screenStream) {
const f: string | null = prompt("fId:");
if (screenPeer && f) {
getScreenStream().then((stream: MediaStream) => {
setScreenStream(stream);
const conn: DataConnection = screenPeer.connect(f);
conn.on("open", (): void => {
const call: MediaConnection = screenPeer.call(
f,
stream,
{
metadata: {
streamType: "screen",
},
}
);
call.on("error", console.error);
call.on("close", (): void => {
console.log("call closed");
});
});
});
}
} else {
screenStream
.getTracks()
.forEach((track: MediaStreamTrack): void =>
track.stop()
);
setScreenStream(undefined);
}
}}
color="inherit"
>
{screenStream ? (
<PausePresentationRounded color="inherit" />
) : (
<PresentToAllRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={() => {
setIsPresentationView((pipv) => !pipv);
}}
color="inherit"
>
{isPresentationView ? (
<ViewComfyOutlined color="inherit" />
) : (
<ViewComfyRounded color="inherit" />
)}
</IconButton>
</>
) : (
<IconButton color="inherit">
{isHandRaised ? (
<PanToolRounded color="inherit" />
) : (
<DoNotTouchRounded color="inherit" />
)}
</IconButton>
)}
<IconButton
color="inherit"
onClick={(): void => {
setIsChatOpen((pico: boolean): boolean => !pico);
}}
>
{isChatOpen ? (
<SpeakerNotesOffRounded color="inherit" />
) : (
<ChatRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={() => {
setIsFullScreen((pifs: boolean): boolean => !pifs);
setIsAdmin(true);
}}
color="inherit"
>
<FullscreenRounded color="inherit" />
</IconButton>
<IconButton
onClick={(): void => {
// const f: string | null = window.prompt("fId");
// getUserStream().then((stream: MediaStream): void => {
// setUserStream(stream);
// if (userPeer && f) {
// const conn: DataConnection = userPeer.connect(f);
// conn.on("open", (): void => {
// const call: MediaConnection = userPeer.call(f, stream, {
// metadata: {
// streamType: "user",
// },
// });
// call.on("error", console.error);
// call.on("close", (): void => {
// console.log("call closed");
// });
// });
// }
// });
const f: string | null = window.prompt("fId");
f && shareUserStream(f);
}}
sx={{
bgcolor: "#550000",
}}
color="error"
>
<div className="flex justify-center items-center grow">
<CallEndRounded />
</div>
</IconButton>
</div>
</div>
</div>
<div className="px-3 py-4">
<div
className={`sm:h-1/4 ${
!isChatOpen && "hidden"
} h-2/5 sm:h-1/4 lg:h-[calc(100%-5rem)] w-full lg:min-w-[20rem]`}
>
<div className="py-2 px-4 bg-primary-400 text-white shadow-lg rounded-t-lg uppercase font-semibold select-none">
live chats
</div>
<div className="flex px-4 h-full overflow-auto flex-col bg-primary-400">
<div className="flex flex-col gap-2 py-2 py-3overflow-auto">
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">This is some chat message</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
</div>
</div>
<div className="py-2 px-4 bg-primary-400 rounded-b-md">
<Input
// value={message}
// onChange={(e) => {
// setMessage(e.target.value);
// }}
placeholder="message"
inputProps={{
className: "no-scrollbar",
}}
fullWidth={true}
className="bg-primary-400"
multiline
maxRows={3}
endAdornment={
<InputAdornment position="end">
<IconButton>
<SendRounded className="cursor-pointer" />
</IconButton>
</InputAdornment>
}
/>
</div>
</div>
</div>
</div>
</div>
);
}
export default LiveLecture;
and finally this is my server code for socket.io connection
socket.on(
JOIN_LECTURE,
({ lectureId, peerId }: { lectureId: string; peerId: string }): void => {
// verify user identity
socket.join(lectureId);
socket.to(lectureId).emit(GET_STREAM, peerId);
console.log(colors.blueBright(`[${socket.id}] joined ${lectureId}`));
// check if the connected user is admin;
// temporarily setting the first user as admin; pink;
const clientsInRoom: Set<string> | undefined =
io.sockets.adapter.rooms.get(lectureId);
const viewerCount: number = (clientsInRoom ? clientsInRoom.size : 0) - 1;
const isAdmin: boolean = !viewerCount;
socket.emit(IS_ADMIN, isAdmin);
// console.log(io.sockets.adapter.rooms);
socket.addListener(disconnect, () => {
console.log("disconnect");
// temporarily transferring admin rights to the next socket; pink;
if (isAdmin) {
const nextAdmin = clientsInRoom?.entries().next();
if (nextAdmin) {
console.log(nextAdmin, "nextAdmin");
io.sockets.to(nextAdmin.value).emit(IS_ADMIN, true);
}
}
});
}
);```
I fixed the error after some research and answer my own question.
In general this error comes from answering the same call multiple times(or so I think from this question's answers). you can see that if you are not facing this problem in ReactJs.
I found out that React calls useEffect twice in order to detect any problems with your code and warn you about them.(find more about in this question)
So, I simply removed the StrictMode wrapper from my main.tsx like:
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<Provider store={store}>
<CookiesProvider>
<ThemeProvider theme={MuiTheme}>
<App />
</ThemeProvider>
<ToastContainer theme="dark" position="bottom-right" />
</CookiesProvider>
</Provider>
);
and now my app works like it should have.
NOTE: Please see the cons of removing StrictMode in react.

How to get child click on paremt in react js

I have a child and parent compoinents as follows,
When I click on the div I am not able to get the alert in parent component but when I keep button in child component I can able to get the aler. Not sure what I am doing, Can any one please help. Thanks.
Child Comp:
const PrimaryNavigation = ({ className, data, childToParent }: IPrimaryNavigation) => {
return (
<div className={`${className} w-full bg-black grid grid-cols-12 gap-4 py-12 pl-12`}>
<div className="relative col-span-2 md:col-span-1 w-5 h-5 md:w-6 md:h-6 cursor-pointer" onClick={() => { childToParent() }}>>
<Image src={searchSvg} layout="fill" objectFit="contain" alt="hamburgerIcon" />
</div>
<div className="relative col-span-2 md:col-span-1 md:w-6 md:h-6 w-5 h-5 cursor-pointer" >
<Image src={hamburgerSvg} layout="fill" objectFit="contain" alt="searchIcon" />
</div>
</div>
)
}
Parent Comp:
const Header = ({ data }: IHeader) => {
const childToParent = () => {
alert('hi')
}
return (
<div>
<header className="w-full md-0 md:md-6 bg-black grid grid-cols-12 gap-4">
<PrimaryNavigation className="col-span-11" data={data.primaryNavigationCollection.items} childToParent={childToParent} />
</header>
</div>
)
}
export default Header

problem in sending or loading data to localstorage, when I do refresh- cart data is not there to show. I am loading data from firebase

I am loading data from firebase, so when I refresh, it takes time to load it. And added data are not present in the cart that,s why I am not getting any product in the cart. But I did follow the same process to store user data, and it,s working fine, which means I am getting a registered profile in console. Can anyone suggest me a solution for getting data in the cart?
sending data:
import { motion } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { MdShoppingBasket } from "react-icons/md";
import { actionType } from "../context/reducer";
import { useGlobalState } from "../context/stateProvider";
import NotFound from "../img/NotFound.svg";
const RowContainer = ({ flag, data, scrollValue }) => {
const [{ cartItems }, dispatch] = useGlobalState();
const [items, setItems] = useState([]);
const rowContainer = useRef();
const addToCart = () => {
dispatch({
type: actionType.SET_CARTITEMS,
cartItems: items,
});
localStorage.setItem("cartItems", JSON.stringify(items));
};
useEffect(() => {
addToCart();
}, [items]);
useEffect(() => {
rowContainer.current.scrollLeft += scrollValue;
}, [scrollValue]);
return (
<div
ref={rowContainer}
className={`w-full my-12 flex items-center ${
flag
? "overflow-x-scroll scrollbar-none scroll-smooth"
: "overflow-x-hidden flex-wrap justify-center"
}`}
>
{data && data.length > 0 ? (
data.map((item) => {
const { id, price, imageURL, calories, title } = item;
return (
<div
key={id}
className="w-350 min-w-[300px] md:min-w-[380px] md:my-10 backdrop-blur-lg mx-1 my-2 lg:mx-2 "
>
<div className="w-full flex flex-row items-center justify-between bg-white rounded-lg drop-shadow-lg py-2 px-4 hover:bg-whiteAlpha min-h-[150px]">
<motion.img
whileHover={{ scale: 1.2 }}
src={imageURL}
alt="img"
className="w-30 max-h-40 -mt-8 duration-100 drop-shadow-2xl"
/>
<div className="flex flex-col items-end justify-end gap-4">
<motion.div
whileTap={{ scale: 0.75 }}
className="w-8 h-8 rounded-md bg-orange-500 hover:bg-orange-600 "
onClick={() => setItems([...new Set(cartItems), item])}
>
<MdShoppingBasket className="text-white text-2xl m-1" />
</motion.div>
<div className=" w-full">
<p className="text-gray-800 md:text-lg text-base text-right">
{title}
</p>
<p className="text-sm text-gray-500 text-right">
<span className="text-orange-600">$</span> {price}
</p>
</div>
</div>
</div>
</div>
);
})
) : (
<div className="w-full flex flex-col items-center justify-center">
<img src={NotFound} className="h-340" alt="" />
<p className="text-center my-2 text-xl text-red-500">No data found</p>
</div>
)}
</div>
);
};
export default RowContainer;
fetching data
export const fetchCart = () => {
const cartInfo =
localStorage.getItem("cartItems") !== "undefined"
? JSON.parse(localStorage.getItem("cartItems"))
: localStorage.clear();
return cartInfo ? cartInfo : [];
};
You are clearing your data for no reason
localStorage.getItem("cartItems") !== "undefined"
should be
localStorage.getItem("cartItems") !== undefined

reactjs scroll to bottom with user defined speed when user click start button

I tried to add auto scrolling div for lyric when user is click play scroll button with speed. There is speed button to increase or decrease speed of scrolling.
So far I only achieved that it is scrolling to bottom.
I use useRef and trackRef.current.scrollIntoView. window.scrollto is not working.
May I know how to scroll div with speed until I can see the all hidden part of the bottom.
here is my code
import React, { useState, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { useQuery } from "#apollo/client";
import { getTrack, getTrackVariable } from "../../gql/track";
import ChordSheetJS from "chordsheetjs";
import { PlayIcon, PauseIcon } from "../../assets/icons/svg_icons";
import { SettingIcon } from "../../assets/icons/svg_icons";
const TrackPage = () => {
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]);
useEffect(() => {
if (play) {
trackRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "start",
});
}
}, [play]);
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 = (e) => {
e.preventDefault();
setPlay(!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 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;
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])

React toggle button after mapping through list

After getting results from api call to Google books i'd like to hide the description paragraphs and have a toggle button using the css class of hidden (tailwinds css). I'm currently just console.logging the elements on the "view description" button & I'm just not sure how to target a single element after looping through the nodeList with my toggleDesc() function
React SearchBar component
import React, { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import axios from 'axios';
import { faSearch } from '#fortawesome/free-solid-svg-icons';
import SearchResult from '../search-result/search-result.component';
const SearchBar = () => {
const [searchTerm, setSearchTerm] = useState('');
const [books, setBooks] = useState({ items: [] });
useEffect(() => {
async function fetchBooks() {
const newRes = await fetch(`${API_URL}?q=${searchTerm}`);
const json = await newRes.json();
const setVis = Object.keys(json).map(item => ({
...item, isDescVisible: 'false'
}))
setBooks(setVis);
}
fetchBooks();
}, []);
const toggleDesc = (id) => {
const newBooks = books.items.map(book => book.id === id ? {...book, isDescVisible: !book.isDescVisible} : book);
console.log(newBooks);
setBooks(newBooks);
}
const onInputChange = (e) => {
setSearchTerm(e.target.value);
};
let API_URL = `https://www.googleapis.com/books/v1/volumes`;
const fetchBooks = async () => {
// Ajax call to API via axios
const result = await axios.get(`${API_URL}?q=${searchTerm}`);
setBooks(result.data);
};
// Handle submit
const onSubmitHandler = (e) => {
// prevent browser from refreshing
e.preventDefault();
// call fetch books async function
fetchBooks();
};
// Handle enter press
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
fetchBooks();
}
}
return (
<div className="search-bar p-8">
<div className="bg-white flex items-center rounded-full shadow-xl">
<input
className="rounded-l-full w-full py-4 px-6 text-gray-700 leading-tight focus:outline-none"
id="search"
type="text"
placeholder="Try 'The Hunt For Red October by Tom Clancy' "
onChange={onInputChange}
value={searchTerm}
onKeyPress={handleKeyPress}
/>
<div className="p-4">
<button
onClick={onSubmitHandler}
className="bg-blue-800 text-white rounded-full p-2 hover:bg-blue-600 focus:outline-none w-12 h-12 flex items-center justify-center"
>
<FontAwesomeIcon icon={faSearch} />
</button>
</div>
</div>
<div className='result mt-8'>
<ul>
<SearchResult books={books} toggleDesc={toggleDesc} />
</ul>
</div>
</div>
);
};
export default SearchBar;
SearchResults Component
import React from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import 'react-lazy-load-image-component/src/effects/blur.css';
import './search-result.styles.scss';
const SearchResult = ({ books, toggleDesc }) => {
return (
<div className="search-result mb-6">
{books.items !== undefined &&
books.items !== null &&
books.items.map((book, index) => {
return (
<div key={index} className="book-info mb-2">
<li className="ml-4">
<div className="flex">
<LazyLoadImage
className="book-img px-4 py-2"
effect="blur"
alt={`${book.volumeInfo.title} book`}
src={`http://books.google.com/books/content?id=${book.id}&printsec=frontcover&img=1&zoom=1&source=gbs_api`}
/>
<div className="flex-1">
<h3 className="text-2xl">{book.volumeInfo.title}</h3>
<div>
<p className="flex">
<button
onClick={() => toggleDesc(book.id)}
className="bg-blue-800 mt-2 text-gray-200 rounded hover:bg-blue-400 px-4 py-3 text-sm focus:outline-none"
type="button"
>
View Description
</button>
</p>
{book.isDescVisible &&
<div
className="block border px-4 py-3 my-2 text-gray-700 desc-content"
>
<p>{book.volumeInfo.description}</p>
</div>
}
</div>
<h3 className="text-xl text-blue-800 mt-2 p-2">
Average time to read:{' '}
</h3>
</div>
</div>
<hr />
</li>
</div>
);
})}
</div>
);
};
export default SearchResult;
console
You will have to add a property to each item of your books to handle the description visibility and change it when you click the button, this is a basic example
useEffect(()=> {
fetch(url).then(res => {
const newRes = res.map(item=> ({ ...item, isDescVisible: 'false' })) // <— add the new property to all items set to false
setBooks(newRes);
})
})
<p className='flex'>
<button
onClick={() => toggleDesc(book.id)} // <—- pass the id or the whole book
className='bg-blue-800 mt-2 text-gray-200 rounded hover:bg-blue-400 px-4 py-3 text-sm focus:outline-none'
type='button'
>
View Description
</button>
</p>
//here show the desc according to the value of the property you added, no need for the hidden class
{book.isDescVisible && <div className='block border px-4 py-3 my-2 text-gray-700 desc-content'>
<p>{book.volumeInfo.description}</p>
</div>}
This function needs to be on the parent where you are setting the state
const toggleDesc = (id) => {
const newBooks = books.map(book => book.id === id ? {...book, isDescVisible: !book.isDescVisible} : book); <-- toggle the property
setBooks(newBooks); <—- save it in the state again
};

Categories

Resources