Неверный вызов перехвата useRef()

Я пытаюсь использовать ссылки динамически, но получаю сообщение об ошибке «Неверный вызов перехватчика. Перехватчики можно вызывать только внутри тела функционального компонента» при использовании useRef. Здесь:

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();
}, []);

ВЕСЬ КОМПОНЕНТ:

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.info(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"
        />
      ))}
    </>
  );
};

КОМПОНЕНТ ПЛАНЕТЫ

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,
};

Любая помощь приветствуется.

Чего вы пытаетесь достичь этой линией?

DBS 29.09.2023 17:54

Пожалуйста, поделитесь кодом всего файла, то есть начиная с первой строки, чтобы мы могли точно видеть, где он используется.

Paurush Sinha 29.09.2023 17:55

Попытка создать ссылку в объекте PlanetRefs для каждой «планеты». (Не уверен, что это правильно)

aitor 29.09.2023 17:57

Готово, я отредактировал вопрос.

aitor 29.09.2023 17:59

Вы пытаетесь нарушить правила. Смотрите это. Если вы пытаетесь запомнить данные, используйте вместо этого useMemo.

Hollyol 29.09.2023 18:18

это часто возникает, но я не могу найти обманщиков. используйте ссылку на массив, объект или карту и т. д.

Mulan 29.09.2023 18:26

Что ты вообще пытаешься? Зачем вам нужен этот реферал за пределами Planet? Учитывая ваш код, я не вижу причины, по которой вам нужно хранить ссылку за пределами самой Планеты. Единственное место, где вы его используете, это внутри самой Планеты.

Euklios 30.09.2023 16:47
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
2
7
86
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Не уверен, чего именно вы пытаетесь достичь здесь.

ref = {planetRefs.current[index]}

ничего не сделает, поскольку planetRefs.current является пустым объектом и останется пустым объектом, поскольку вы не присваиваете ему никакого значения.

planetRefs.current[camelCaseSlug] = useRef();

также ничего не сделает, поскольку вы не можете использовать хуки реагирования внутри функции, которая не является компонентом реагирования.


Кстати, почему вы назначаете значение ref как camelCaseSlug, но при рендеринге пытаетесь получить к нему доступ с помощью index?


Возможное решение

Я думаю, вам следует вместо этого использовать ссылку обратного вызова, чтобы получить доступ к визуализируемым элементам:

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

Примечание. Не забудьте обернуть компонент PlanetforwardRef.

Редактировать:

Вы можете полностью удалить часть planetRefs.current[camelCaseSlug] = useRef();. Если вы предпочитаете хранить refs в planetRefs в виде слизней, просто немного измените ссылку обратного вызова:

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

   planetRefs.current[camelCaseSlug] = ref;
}}

Спасибо большое за ответ 🙏. Боюсь, это слишком много для моего уровня. Я не понимаю ссылку обратного вызова. Когда я реализую это, ссылка недоступна в компоненте Planet. Я ценю ваши усилия, правда.

aitor 29.09.2023 19:30

@aitor Чтобы получить доступ к ссылке, вам нужно использовать функцию forwardRef в вашем компоненте Planet. Пожалуйста, проверьте: act.dev/reference/react/forwardRef

kind user 29.09.2023 19:33

Да, оно у меня есть: export const Planet = forwardRef(function Planet(props, ref) {}

aitor 29.09.2023 19:35

@aitor Не забудьте передать значение ref понравившемуся компоненту. Например <div ref = {ref}>{...}</div>. Ref должен быть присвоен какому-то элементу.

kind user 29.09.2023 19:38

@aitor Было бы лучше, если бы ты просто показал мне свой Planet-компонент :)

kind user 29.09.2023 19:40

пишу комментарий! одна минута :)

aitor 29.09.2023 19:43

Давайте продолжим обсуждение в чате.

aitor 29.09.2023 20:07

@aitor Пожалуйста, просто отредактируйте свой вопрос и вставьте компонент Planet :)

kind user 29.09.2023 20:08

@aitor Спасибо за редактирование. Я не уверен, почему вы хотите передавать ссылки динамически. Вы хотите иметь доступ к компоненту Planet от родителя? Если нет, вам следует переместить использование useRef в компонент Planet, как это показано на: docs.pmnd.rs/react-three-fiber/getting-started/introduction (посмотрите здесь, useRef используется в компоненте Box, как ваш Planet).

kind user 30.09.2023 23:45

Извините, я только что увидел ваш комментарий. Да, ты прав. Я уже видел, в чем была моя ошибка. Я пытаюсь обновить позиции родительского компонента, когда в этом нет необходимости. Я могу выполнить расчеты в компоненте «Планета». Это также сделает весь код чище. Я очень благодарен вам за всю помощь, которую я получил. Это было очень полезно.

aitor 04.10.2023 15:27

Вы вызываете перехватчик условно для своего компонента.

https://legacy.reactjs.org/docs/hooks-rules.html

Не вызывайте хуки внутри циклов, условий или вложенных функций. Вместо этого всегда используйте хуки на верхнем уровне вашей функции React, прежде чем делать какие-либо ранние возвраты. Следуя этому правилу, вы гарантируете, что хуки будут вызываться в одном и том же порядке каждый раз при рендеринге компонента.

Ответ принят как подходящий

Как уже упоминалось здесь, вы просто не можете использовать перехватчики в любой форме потока управления. Так что нет if, else, for, while, forEach, callback, ...

Вы можете использовать ref = {} с обратным вызовом, например:

<Planet
          key = {index}
          ref = {(ref) => {planetRefs[index] = ref}}

но кроме этого, нет возможности сохранить рефери такими, какие они у вас есть.

Редактировать: Я до сих пор не уверен, чего вы здесь пытаетесь достичь, но нет необходимости иметь ссылку на Планету за пределами самой Планеты. (Честно говоря, реферал может вообще не понадобиться) Вы можете просто обновить ротацию в Planet и покончить с этим.

export const Planet = function Planet(props, ref) {
  // ...

  const ref = useRef();
  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}
      // ...
    >
      // ...
  );
};

// ...

Альтернативно, если рефы действительно нужны снаружи, а это, на мой взгляд, редко, создайте массив/объект и передайте его компоненту Planet.

const planets = useRef({});

// ...

planets.forEach((planet) => {
  return <Planet key = {planet.slug} planets = {planets.current} ... />
});


function Planet(props) {
  const ref = useRef();
  useEffect(() => {
    // Add it to the list of planets when component is rendered
    props.planets[props.slug] = ref;
  }, () => {
    // Remove when no longer needed
    props.planets[props.slug] = undefined;
  });

  // ...
}

Вы сможете получить к нему доступ, используя

  planets.current[slug].current

Проблема в том, что мне нужно, чтобы ссылка была доступна в компоненте планеты. Если внутри Planet да, то console.info(ref); Возвращает функцию обратного вызова вместо ссылки.

aitor 30.09.2023 09:55

@aitor Я добавил решение, реализующее эту проблему. Но я действительно думаю, что вам следует подумать об этом, если вам действительно нужен рефери за пределами Планеты. Обычно это показатель плохой сепарации (по моему опыту)

Euklios 30.09.2023 17:02

Абсолютно прав. Большое спасибо. Правильный подход — хранить обновления позиций внутри компонента.

aitor 01.10.2023 19:20

Другие вопросы по теме