Как управлять React refs в цикле для нескольких копий компонента
При разработке на React часто возникает задача отрендерить несколько копий одного компонента и получить ссылки (refs) на каждый из них. В этой статье мы разберём типичную ошибку, когда console.log возвращает null, и предложим правильное решение с использованием forwardRef и useCallback.
Почему ref возвращает null при рендере в цикле
В исходном коде используется useRef([]) и функция setRef, которая присваивает элемент по индексу. Проблема в том, что ref callback вызывается дважды: при монтировании и при размонтировании. Во время размонтирования в setRef передаётся null, что затирает сохранённую ссылку. Кроме того, при изменении numComp массив compRefs.current заполняется null, а старые ссылки теряются.
Правильное решение: используем useCallback и useEffect
Чтобы избежать затирания refs, нужно обновлять массив ссылок только при монтировании и корректно обрабатывать очистку. Вот рабочий пример:
const [numComp, setNumComp] = useState(1);
const compRefs = useRef([]);
useEffect(() => {
// Инициализируем массив нужной длины
compRefs.current = compRefs.current.slice(0, numComp);
while (compRefs.current.length < numComp) {
compRefs.current.push(null);
}
}, [numComp]);
const setRef = useCallback((el, index) => {
compRefs.current[index] = el;
}, []);
const handleClick = useCallback((index) => {
if (compRefs.current[index]) {
console.log(compRefs.current[index]);
}
}, []);
return (
<>
{[...Array(numComp)].map((_, index) => (
<CompExample
key={index}
ref={(el) => setRef(el, index)}
onClick={() => handleClick(index)}
/>
))}
</>
);Ключевые моменты для корректной работы
- key - обязательно добавьте уникальный ключ для каждого элемента в цикле, чтобы React правильно отслеживал изменения.
- forwardRef - убедитесь, что CompExample обёрнут в React.forwardRef и передаёт ref на корневой DOM-элемент.
- useCallback - оберните setRef и handleClick в useCallback, чтобы избежать лишних ререндеров.
- Очистка ref - не используйте обнуление массива через
fill(null), так как это сбрасывает уже сохранённые ссылки.
Альтернативный подход: хранение refs в state
Если вам нужно динамически добавлять или удалять компоненты, рассмотрите хранение refs в useState с объектом:
const [refs, setRefs] = useState({});
const setRef = useCallback((el, index) => {
setRefs(prev => ({...prev, [index]: el}));
}, []);
const handleClick = useCallback((index) => {
console.log(refs[index]);
}, [refs]);Этот способ гарантирует, что ссылки не затираются, но может вызывать ререндеры при каждом изменении refs.
Проверка и отладка
Чтобы убедиться, что refs работают корректно, выведите массив compRefs.current в консоль после рендера. Если все элементы - это DOM-узлы или экземпляры компонентов, значит, решение верное. В противном случае проверьте, что forwardRef настроен правильно и что ref не передаётся на функциональный компонент без обёртки.
Заключение
Проблема с null в консоли возникает из-за неправильного управления массивом refs и игнорирования фаз жизненного цикла. Используйте useCallback для функций, useEffect для синхронизации длины массива и обязательно добавляйте key в цикле. Теперь вы можете легко управлять refs для любого количества копий компонента.