Skip to main content

Overview

The RIS Gran Chimú app provides several custom React hooks for common functionality. These hooks encapsulate complex logic and provide clean APIs for components.

useAuth

Manages user authentication state, login, and logout functionality.

Location

src/hooks/useAuth.tsx

Type Definition

type User = {
  id: string;
  name: string;
  role: string;
};

type AuthContextType = {
  user: User | null;
  signIn: (email: string, password: string) => Promise<void>;
  signOut: () => Promise<void>;
  loading: boolean;
};

Usage

import { useAuth } from '@/src/hooks/useAuth';

export default function ProfileScreen() {
  const { user, signOut, loading } = useAuth();

  if (loading) {
    return <ActivityIndicator />;
  }

  return (
    <View>
      <Text>Welcome, {user?.name}</Text>
      <Text>Role: {user?.role}</Text>
      <Button title="Sign Out" onPress={signOut} />
    </View>
  );
}

API Reference

user
User | null
Current authenticated user or null if not logged in
signIn
(email: string, password: string) => Promise<void>
Authenticates user with email and password. Stores token and redirects to dashboard on success.
signOut
() => Promise<void>
Logs out the current user, clears stored credentials, and redirects to landing page
loading
boolean
true while authentication state is being initialized or during login

Features

  • Stores JWT token securely in AsyncStorage
  • Automatically applies token to API requests via setAuthToken()
  • Validates token on app startup
  • Decodes JWT to check expiration time
  • Schedules automatic logout when token expires
  • Shows alert to user before logging out
  • Provides user-friendly error messages for different scenarios:
    • 401: “Correo o contraseña incorrectos”
    • 404: “Usuario no encontrado”
    • 500: “Error del servidor”
    • Network errors: Connection failure messages
  • Restores user session from AsyncStorage on app launch
  • Validates stored token with backend /auth/me endpoint
  • Falls back to local validation if backend unavailable

Login Flow

const signIn = async (email: string, password: string) => {
  const { user, token } = await apiClient.post('/auth/login', { email, password });
  
  // Map backend fields to frontend User type
  const mappedUser = {
    id: String(user.id),
    name: user.nombre,
    role: user.rol
  };
  
  // Store in AsyncStorage
  await AsyncStorage.setItem(USER_STORAGE_KEY, JSON.stringify({ user: mappedUser, token }));
  
  // Set token for API requests
  setAuthToken(token);
  
  // Schedule auto-logout on expiry
  scheduleExpiry(token);
  
  // Redirect to dashboard
  router.replace('/(main)/dashboard');
};
useAuth must be used within an <AuthProvider>. The provider is typically placed in _layout.tsx.

useDebounce

Delays updating a value until after a specified time has passed since the last change.

Location

src/hooks/useDebounce.ts

Signature

function useDebounce(value: string, delay: number): string

Implementation

src/hooks/useDebounce.ts
import { useState, useEffect } from 'react';

export function useDebounce(value: string, delay: number) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

Usage Example

Search Component
import { useState } from 'react';
import { useDebounce } from '@/src/hooks/useDebounce';
import { TextInput } from 'react-native';

export default function SearchScreen() {
  const [searchQuery, setSearchQuery] = useState('');
  const debouncedQuery = useDebounce(searchQuery, 500);

  // This effect only runs 500ms after user stops typing
  useEffect(() => {
    if (debouncedQuery) {
      performSearch(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <TextInput
      value={searchQuery}
      onChangeText={setSearchQuery}
      placeholder="Search normas..."
    />
  );
}
Common use cases: search inputs, API calls on text input, validation checks

useThemeColor

Retrieves the appropriate color value based on the current theme (light/dark mode).

Location

src/hooks/useThemeColor.ts

Signature

function useThemeColor(
  props: { light?: string; dark?: string },
  colorName: keyof typeof Colors.light & keyof typeof Colors.dark
): string

Implementation

src/hooks/useThemeColor.ts
import { Colors } from '@/src/constants/Colors';
import { useColorScheme } from '@/src/hooks/useColorScheme';

export function useThemeColor(
  props: { light?: string; dark?: string },
  colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
  const theme = useColorScheme() ?? 'light';
  const colorFromProps = props[theme];

  if (colorFromProps) {
    return colorFromProps;
  } else {
    return Colors[theme][colorName];
  }
}

Parameters

props
{ light?: string; dark?: string }
Optional color overrides for light and dark modes
colorName
string
Key from Colors constant: 'text', 'background', 'tint', 'icon', 'tabIconDefault', 'tabIconSelected'

Usage Example

import { useThemeColor } from '@/src/hooks/useThemeColor';
import { View, Text } from 'react-native';

export default function CustomCard() {
  const backgroundColor = useThemeColor({}, 'background');
  const textColor = useThemeColor({ light: '#333', dark: '#fff' }, 'text');
  const iconColor = useThemeColor({}, 'icon');

  return (
    <View style={{ backgroundColor, padding: 16 }}>
      <Text style={{ color: textColor }}>Card Content</Text>
    </View>
  );
}

useColorScheme

Returns the current color scheme (light or dark mode).

Location

src/hooks/useColorScheme.ts

Implementation

export { useColorScheme } from 'react-native';
This is a re-export of React Native’s built-in useColorScheme hook. On web, a custom implementation in useColorScheme.web.ts is used instead.

Usage

import { useColorScheme } from '@/src/hooks/useColorScheme';

export default function ThemeToggle() {
  const colorScheme = useColorScheme();

  return (
    <Text>
      Current theme: {colorScheme === 'dark' ? 'Dark Mode' : 'Light Mode'}
    </Text>
  );
}

useFetchNormas

Fetches the list of normas (regulations) from the API.

Location

src/hooks/useFetchNormas.ts

Return Type

{
  normas: Norma[];
  loading: boolean;
  error: any;
}

Implementation

src/hooks/useFetchNormas.ts
import { useState, useEffect } from 'react';
import apiClient from '../services/apiClient';
import { Norma } from '../types/Norma';

export function useFetchNormas() {
  const [normas, setNormas] = useState<Norma[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchNormas = async () => {
      try {
        setLoading(true);
        const res = await apiClient.get('/normas');
        const data = (res.data as { data: Norma[] }).data || [];
        setNormas(data);
      } catch (err: any) {
        setError(err);
        console.error('Error al cargar normas:', err);
      } finally {
        setLoading(false);
      }
    };

    fetchNormas();
  }, []);

  return { normas, loading, error };
}

Norma Type

src/types/Norma.ts
export interface Norma {
  id_norma: string;
  anho: string;
  descripcion: string;
}

Usage Example

import { useFetchNormas } from '@/src/hooks/useFetchNormas';
import { FlatList, ActivityIndicator } from 'react-native';
import { ThemedText } from '@/src/components/ThemedText';

export default function NormasListScreen() {
  const { normas, loading, error } = useFetchNormas();

  if (loading) {
    return <ActivityIndicator size="large" />;
  }

  if (error) {
    return <ThemedText>Error loading normas</ThemedText>;
  }

  return (
    <FlatList
      data={normas}
      keyExtractor={(item) => item.id_norma}
      renderItem={({ item }) => (
        <View>
          <ThemedText type="subtitle">{item.descripcion}</ThemedText>
          <ThemedText>Year: {item.anho}</ThemedText>
        </View>
      )}
    />
  );
}
This hook fetches data only once when the component mounts. For real-time updates, consider adding a refresh function or using a state management solution.

Best Practices

Error Handling

Always handle the error state from data-fetching hooks like useFetchNormas

Loading States

Show loading indicators when loading is true to improve UX

Debounce Input

Use useDebounce for search inputs to reduce unnecessary API calls

Auth Protection

Check user from useAuth to conditionally render protected content

Build docs developers (and LLMs) love