frontend: keep animated values in state so render never touches a ref
Pedro Lucas Porcellis porcellis@eletrotupi.com · 12 days ago
2d27b5593cd012f3160a84c9560b68e2590925d8 Browse filesParents
4f91bffChanged files
3
Additions
+10
Deletions
-10
frontend/app/auth/login.tsx
1 -
import React, { useState, useEffect, useRef } from "react";
1 +
import React, { useState, useEffect } from "react";
2 2
import {
3 3
View,
4 4
StyleSheet,
5 5
ScrollView,
6 6
KeyboardAvoidingView,
7 7
Animated,
8 -
Easing
8 +
Easing,
9 +
Image,
9 10
} from "react-native";
10 11
import { Text } from "@/components/ui/Text";
11 12
import { Link, router } from "expo-router";
12 -
import { Image } from "react-native";
13 13
import { SafeAreaView } from "react-native-safe-area-context";
14 14
import { StatusBar } from "expo-status-bar";
15 15
import { useThemeColor } from "@/hooks/use-theme-color";
16 16
import { Button, Input } from "@/components/ui";
17 17
import { useAuth } from "@/context/AuthContext";
32 32
const { showToast } = useToast();
33 33
const textColor = useThemeColor({}, "text");
34 34
const backgroundColor = useThemeColor({}, "background");
35 35
const tintColor = useThemeColor({}, "tint");
36 36
37 -
const spinValue = useRef(new Animated.Value(0)).current;
37 +
const [spinValue] = useState(() => new Animated.Value(0));
38 38
useEffect(() => {
39 39
const animation = Animated.loop(
40 40
Animated.timing(spinValue, {
41 41
toValue: 1,
42 42
duration: 12000, // 12s per full turn (slow)
261 261
},
262 262
footerText: {
263 263
fontSize: 14,
264 264
},
265 265
});
frontend/components/ui/AvatarUpload.tsx
1 -
import React, { useState, useRef } from 'react';
1 +
import React, { useState } from 'react';
2 2
import {
3 3
View,
4 4
Image,
5 5
TouchableOpacity,
6 6
Alert,
30 30
onRemoveSuccess,
31 31
}) => {
32 32
const [preview, setPreview] = useState<string | null>(currentAvatar);
33 33
const { showToast } = useToast();
34 34
const [isLoading, setIsLoading] = useState<boolean>(false);
35 -
const scaleAnim = useRef(new Animated.Value(1)).current;
35 +
const [scaleAnim] = useState(() => new Animated.Value(1));
36 36
37 37
const handleImagePick = async (useCamera: boolean = false): Promise<void> => {
38 38
try {
39 39
const result = useCamera
40 40
? await ImagePicker.launchCameraAsync({
299 299
textTransform: 'uppercase',
300 300
},
301 301
});
302 302
303 303
export default AvatarUpload;
frontend/components/ui/Toast.tsx
1 -
import React, { useEffect, useRef } from 'react';
1 +
import React, { useEffect, useState } from 'react';
2 2
import { Animated, StyleSheet } from 'react-native';
3 3
import { Text } from '@/components/ui/Text';
4 4
import { useSafeAreaInsets } from 'react-native-safe-area-context';
5 5
6 6
export type ToastVariant = 'error' | 'success' | 'warning';
17 17
warning: '#F97316',
18 18
};
19 19
20 20
export function Toast({ message, variant, visible }: ToastProps) {
21 21
const insets = useSafeAreaInsets();
22 -
const translateY = useRef(new Animated.Value(-120)).current;
23 -
const opacity = useRef(new Animated.Value(0)).current;
22 +
const [translateY] = useState(() => new Animated.Value(-120));
23 +
const [opacity] = useState(() => new Animated.Value(0));
24 24
25 25
useEffect(() => {
26 26
if (visible) {
27 27
Animated.parallel([
28 28
Animated.spring(translateY, {
49 49
duration: 200,
50 50
useNativeDriver: true,
51 51
}),
52 52
]).start();
53 53
}
54 -
}, [visible]);
54 +
}, [visible, translateY, opacity]);
55 55
56 56
return (
57 57
<Animated.View
58 58
style={[
59 59
styles.container,
89 89
fontSize: 14,
90 90
fontWeight: '500',
91 91
lineHeight: 20,
92 92
},
93 93
});