Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid hook call of useRef()

I'm trying to assing refs dinamically, but I get the error "Invalid hook call. Hooks can only be called inside of the body of a function component" when useRef. Here:

const [subsistemaPlanetario, setSubsistemaPlanetario] = useState([]);
const planetRefs = useRef({});

useEffect(() => {
  async function fetchSubsistemaPlanetario() {
    try {
      const fetchedSubsistemaPlanetario = await getSubsistemaPlanetario();
      setSubsistemaPlanetario(fetchedSubsistemaPlanetario);

      fetchedSubsistemaPlanetario.forEach((planeta) => {
        const camelCaseSlug = planeta.padre.slug.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
        planetRefs.current[camelCaseSlug] = useRef(); // <------THIS LINE DROP AN ERROR
      });
    } catch (error) {
      console.error(error);
    }
  }

  fetchSubsistemaPlanetario();
}, []);

ENTIRE COMPONENT:

import {useFrame} from '@react-three/fiber';
import React, {useRef, useEffect, useState} from 'react';
import {Planet} from './Planet.jsx';
import {Satellite} from './Satellite.jsx';
import {Orbiter} from './utils/Orbiter.js';
import {calculateOrbitalPeriod} from './utils/calculateOrbitalPeriod.js';

import {getSubsistemaPlanetario} from './utils/getSubsistemaPlanetario.js';

export const SubsistemaPlanetario = function SubsistemaPlanetario(props) {
  let running = true;
  let stopRunning = () => (running = false);
  let startRunning = () => (running = true);

  const [subsistemaPlanetario, setSubsistemaPlanetario] = useState([]);
  const planetRefs = useRef({});

  useEffect(() => {
    // Obtener el subsistema planetario cuando el componente se monta
    async function fetchSubsistemaPlanetario() {
      try {
        const fetchedSubsistemaPlanetario = await getSubsistemaPlanetario();
        
        setSubsistemaPlanetario(fetchedSubsistemaPlanetario);

        fetchedSubsistemaPlanetario.forEach((planeta) => {
          const camelCaseSlug = planeta.padre.slug.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());

          planetRefs.current[camelCaseSlug] = useRef();
          console.log(planetRefs);
        });
      } catch (error) {
        console.error(error);
      }
    }

    fetchSubsistemaPlanetario();
  }, []);

  return (
    <>
      {subsistemaPlanetario.map((planetaPadre, index) => (
        <Planet
          key={index}
          scale={0.5}
          ref={planetRefs.current[index]}
          stopRunning={stopRunning}
          startRunning={startRunning}
          textureType="haumea"
          linkTo="areas"
          linkToLabel="Areas"
        />
      ))}
    </>
  );
};

PLANET COMPONENT

import {forwardRef, useRef, useEffect, useContext, useState} from 'react';
import PropTypes from 'prop-types';
import {useTexture} from '@react-three/drei';
import {useFrame} from '@react-three/fiber';
import barba from '@barba/core';
import {solapaContentAbrir, solapaContentCerrar} from './utils/Solapa.js';
import {planets} from './utils/arrayTexturas.js';

// Define un objeto que mapea los tipos de textura a las rutas de los archivos de textura.
const textureMap = {};

for (const planet of planets) {
  textureMap[planet] = `./app/themes/sage/resources/scripts/cosmos/components/textures/${planet}-512.jpg`;
}

export const Planet = forwardRef(function Planet(props, ref) {
  // Obtén la ruta de la textura según el tipo especificado en props.textureType.
  const texturePath = textureMap[props.textureType] || textureMap.sand;
  const texture = useTexture(texturePath);

  let rotationX = Math.random();
  let rotationY = Math.random();

  useFrame((state, delta) => {
    ref.current.rotation.x += rotationX * delta;
    ref.current.rotation.y += rotationY * delta;
  });

  return (
    <mesh
      {...props}
      ref={ref}
      castShadow
      receiveShadow
      onPointerEnter={(event) => {
        props.stopRunning();
        document.body.style.cursor = 'pointer';
        solapaContentAbrir('Sección', props.linkToLabel);
        event.stopPropagation();
      }}
      onPointerLeave={(event) => {
        props.startRunning();
        document.body.style.cursor = 'default';
        solapaContentCerrar();
        event.stopPropagation();
      }}
      onClick={(event) => {
        barba.go(props.linkTo);
      }}
    >
      <sphereGeometry />
      <meshStandardMaterial map={texture} />
    </mesh>
  );
});

Planet.propTypes = {
  stopRunning: PropTypes.func,
  startRunning: PropTypes.func,
  textureType: PropTypes.oneOf(['haumea', 'mars', 'neptune', 'venus', 'mercury', 'jupiter', 'saturn']),
  userData: PropTypes.object,
  radius: PropTypes.number,
  linkTo: PropTypes.string,
  linkToLabel: PropTypes.string,
};

Any help appreciated.

like image 239
aitor Avatar asked Mar 06 '26 18:03

aitor


1 Answers

Not sure exactly what you are trying to achieve here.

ref={planetRefs.current[index]}

will do nothing since planetRefs.current is an empty object and will remain as an empty object since you are not assigning any value to it.

planetRefs.current[camelCaseSlug] = useRef();

will also do nothing since you can't use react hooks inside a function that is not a react component.


By the way - why you are assigning the ref value as camelCaseSlug but in the render you are trying to access it by index?


Possible solution

I think you should be using a callback ref instead, to get the access to the rendered elements:

ref={(ref) => {
   planetRefs.current[index] = ref;
}}

Note: Remember to wrap your Planet component with forwardRef.

Edit:

You can remove the planetRefs.current[camelCaseSlug] = useRef(); part completely. If you prefer to keep the refs in the planetRefs as slugs, just modify the callback ref a bit:

ref={(ref) => {
   const camelCaseSlug = planetaPadre.padre.slug
      .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());

   planetRefs.current[camelCaseSlug] = ref;
}}
like image 128
kind user Avatar answered Mar 08 '26 08:03

kind user



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!