Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

React Hooks must be called in the exact same order in every component render problem?

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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>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>
    );
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading