I have implemented keyboard key or button navigation in a list of items. Locally I don’t get any errors, in the build phase I get the following errors:
Error: React Hook "useRef" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
Error: React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
This is my code:
'use client'
import { FixedSizeList } from 'react-window'
import useIsMobile from '../../../../../hooks/useIsMobile'
import PackagesCard from './packagesCard'
import AutoSizer from 'react-virtualized-auto-sizer'
import { useRef, useState, useEffect } from 'react'
import Image from 'next/image'
const VirtualPackagesCarousel = ({ data }) => {
const { mobile } = useIsMobile()
if (!data || data.length === 0) {
return (
<div className="h-[350px] flex text-neutrals-50 justify-center align-middle">
There are no courses here yet!
</div>
)
}
const listRef = useRef(null)
const [selection, setSelection] = useState(null)
const handleKeyDown = (e) => {
if (e.key === 'ArrowRight') {
e.preventDefault()
if (selection === null) {
setSelection(0)
} else {
setSelection(Math.min(data.length - 1, selection + 1))
}
}
if (e.key === 'ArrowLeft') {
e.preventDefault()
if (selection === null) {
setSelection(0)
} else {
setSelection(Math.max(0, selection - 1))
}
}
}
const handleNext = () => {
if (selection === null) {
setSelection(0)
} else {
setSelection(Math.min(data.length - 1, selection + 1))
}
}
const handleBack = () => {
if (selection === null) {
setSelection(0)
} else {
setSelection((prev) => Math.max(0, prev - 1))
}
}
const handleBlur = () => {
setSelection(null)
}
useEffect(() => {
const list = listRef.current
if (!list) return
if (selection === null) return
list.scrollToItem(selection)
}, [selection])
return (
<div
tabIndex={0}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
className="w-full flex flex-col gap-5 outline-none border-transparent focus:border-transparent focus:ring-0"
>
<div className="flex gap-3 justify-end visible mobile:invisible">
<Image
src="/images/arrowLeft.svg"
alt="arrowBack"
width="10"
height="10"
className="cursor-pointer invert"
onClick={handleBack}
/>
<Image
src="/images/arrowRight.svg"
alt="arrowNext"
width="10"
height="10"
className="cursor-pointer invert "
onClick={handleNext}
/>
</div>
<AutoSizer>
{({ _, width }) => (
<FixedSizeList
height={400}
itemCount={data.length}
itemSize={mobile ? 300 : 600}
width={width}
layout="horizontal"
ref={listRef}
className="outline-none border-transparent focus:border-transparent focus:ring-0"
>
{({ index, style }) => (
<div className="px-2.5 mobile:px-1" style={style}>
<PackagesCard
data={data[index]}
key={data[index].id}
index={index}
setSelection={setSelection}
selection={selection}
/>
</div>
)}
</FixedSizeList>
)}
</AutoSizer>
</div>
)
}
export default VirtualPackagesCarousel
I don’t understand where I’m going wrong, could someone help me understand where is the error?
Why do I not receive these errors locally and yes in the build phase?
>Solution :
Move the first if statement after all the hook calls. See Rules of Hooks.
As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them.
const { mobile } = useIsMobile();
const listRef = useRef(null);
const [selection, setSelection] = useState(null);
useEffect(() => {
const list = listRef.current
if (!list) return
if (selection === null) return
list.scrollToItem(selection)
}, [selection]);
if (!data || data.length === 0) {
return (
<div className="h-[350px] flex text-neutrals-50 justify-center align-middle">
There are no courses here yet!
</div>
);
}