#javascript #reactjs #react-native
Вопрос:
Поэтому я создал верхнюю липкую панель для отображения вкладок для разных категорий продуктов. Панель будет появляться и исчезать в зависимости от того, как далеко внизу экрана находится пользователь, а также будет меняться индикатор вкладки в соответствии с отображаемой категорией.
Это работает отлично и классно. За исключением случаев, когда пользователь опускает экран, чтобы обновить/обновить данные и количество изменений категории. Когда это происходит, приложение вылетает с этой ошибкой:
Error: Rendered more** hooks than during the previous render.
** Если в новых данных меньше «вкладок», то ошибка отображается меньше, а не больше.
Я знаю, откуда берется ошибка, я просто не знаю, как ее исправить. Это происходит при создании refs
для каждой вкладки с помощью useRef()
крючка. При повторном рендеринге приложения (из-за обновления данных) и изменении количества подключений происходит сбой. Есть ли способ уничтожить предыдущие ссылки? У меня нет идей, и любое предложение будет высоко оценено.
Tabs.js — Функциональный компонент
const Tabs = ({ tabs, tabIndex, show, onPress }) => {
const tabsContainerRef = useRef(null);
const tabsScrollViewRef = useRef(null);
const indicatorRef = useRef(null);
const translateY = useRef(new Animated.Value(-100)).current;
const translateX = useRef(new Animated.Value(0)).current;
const indicatorWidth = useRef(new Animated.Value(1)).current;
const [hasLoaded, setHasLoaded] = useState(false);
// THIS IS CAUSING THE ERROR !
tabs = tabs.map((tab) => {
return { ...tab, ref: useRef() };
});
useEffect(() => {
if (!hasLoaded) {
setHasLoaded(true);
}
}, []);
useEffect(() => {
if (hasLoaded) {
tabs[0].ref?.current.measure((x, y, width, height) => {
Animated.timing(indicatorWidth, {
toValue: width,
duration: 250,
useNativeDriver: false,
}).start();
});
}
}, [hasLoaded]);
useEffect(() => {
Animated.timing(translateY, {
toValue: show ? 0 : -100,
duration: 250,
useNativeDriver: true,
}).start();
}, [show]);
useEffect(() => {
setTimeout(() => {
if (show) {
tabs[tabIndex].ref?.current.measureLayout(
tabsScrollViewRef.current,
(x, y, width, height) => {
// console.log(x, y, width, height);
Animated.timing(indicatorWidth, {
toValue: width,
duration: 250,
useNativeDriver: false,
}).start();
Animated.timing(translateX, {
toValue: x,
duration: 250,
useNativeDriver: false,
}).start();
tabs[tabIndex].ref?.current.measure(
(tabX, tabY, tabWidth, tabHeight, tabPageX, tabPageY) => {
// console.log(tabX, tabY, tabWidth, tabHeight, tabPageX, tabPageY);
if (tabPageX < 0 || tabPageX > SCREEN_WIDTH)
tabsScrollViewRef.current.scrollTo({
x,
animated: true,
});
}
);
},
(error) => {
console.log("Failed to track tab indicator: ", error);
}
);
}
}, 250);
}, [tabIndex]);
return (
<Animated.View
ref={tabsContainerRef}
style={[
styles.containerTabs,
{
transform: [{ translateY }],
},
]}
>
<ScrollView
ref={tabsScrollViewRef}
style={styles.containerTabsScrollView}
contentContainerStyle={styles.contentTabsScrollView}
showsHorizontalScrollIndicator={false}
horizontal
>
{tabs.map((tab, index) => {
return (
<TouchableOpacity
ref={tab.ref}
key={index}
onPress={() => onPress(index)}
style={styles.containerTab}
>
<Animated.View
style={
tabIndex === index
? [styles.containerTabContent, { opacity: 1 }]
: styles.containerTabContent
}
>
<Text style={styles.textTab}>{tab.title}</Text>
</Animated.View>
</TouchableOpacity>
);
})}
<Animated.View
ref={indicatorRef}
style={{
height: 5,
backgroundColor: ClientConfig.colors.primary,
position: "absolute",
bottom: 0,
width: indicatorWidth,
transform: [{ translateX }],
}}
></Animated.View>
</ScrollView>
</Animated.View>
);
};
Дополнительный запрос
Компонент, вызывающий мой компонент Tabs
, является типом компонента класса, могу ли я создать ссылки на вкладки с его помощью (с помощью React.createRef()
), отправить его на мои вкладки (функциональный компонент) и использовать его там? Я попытался отправить refs
данные с вкладками через реквизит, и мне это не понравилось.
Спасибо.
Комментарии:
1. Будет ли иметь значение, если я сделаю его компонентом класса?? 🤔
Ответ №1:
После многих испытаний я нашел решение. Все, что мне нужно было сделать, это убедиться, что компонент был отключен при обновлении данных, чтобы все крючки были очищены React. Поэтому, когда я вызвал компонент Tabs
, я добавил условный оператор !refreshing
примерно так:
{!refreshing amp;amp; tabs.length amp;amp; (
<Tabs
tabs={tabs}
tabIndex={tabIndex}
show={showTabs}
onPress={this.onTabPress}
/>
)}
Это окончательно решило проблему :). Приведенный выше код для вкладок нуждался в некоторой тонкой настройке после этого изменения, ширина индикатора не устанавливается после обновления, но это нормально, легко исправить.