eletrotupi / tcc/ commit / 2d27b55

frontend: keep animated values in state so render never touches a ref

Pedro Lucas Porcellis porcellis@eletrotupi.com · 12 days ago 2d27b5593cd012f3160a84c9560b68e2590925d8 Browse files
Parents 4f91bff
Changed 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
});