Custom React Hooks - bcgov/lcfs GitHub Wiki
Custom React Hooks
This page documents commonly used custom React Hooks within the LCFS frontend application. Custom hooks are a way to extract component logic into reusable functions. This addresses requirements for ticket #2410 and is based on the original "10. Custom Hooks" wiki page if such content existed (though it was only listed as a title).
1. Purpose of Custom Hooks
- Share Logic: Reuse stateful logic between multiple components without resorting to higher-order components or render props.
- Simplify Components: Keep component code cleaner and focused on rendering by extracting complex logic.
- Improve Readability: Make logic easier to follow and understand.
- Facilitate Testing: Custom hooks can often be tested in isolation.
2. Location
Custom hooks are typically located in the frontend/src/hooks/
directory.
3. Naming Convention
Custom hooks should always be prefixed with use
(e.g., useUserData
, useFormValidation
, useApiService
).
4. Common Custom Hooks (Examples & Placeholders)
(This section should be populated by identifying and documenting actual custom hooks from frontend/src/hooks/
. Below are common patterns and examples based on the project's library usage.)
useApiService
or similar)
4.1. API Service Hook (Many applications create a custom hook to encapsulate axios
instance creation, including base URL, default headers, and potentially interceptors for token refresh or global error handling.
// frontend/src/hooks/useApiService.js (Conceptual Example)
import axios from 'axios';
import { useKeycloak } from '@react-keycloak/web'; // If Keycloak token is needed
// Define your API base URL (ideally from an environment variable)
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api';
export const useApiService = () => {
const { keycloak, initialized } = useKeycloak();
const client = axios.create({
baseURL: API_BASE_URL,
});
// Add a request interceptor to include the auth token
client.interceptors.request.use(
(config) => {
if (initialized && keycloak.token) {
config.headers.Authorization = `Bearer ${keycloak.token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Optional: Add response interceptor for token refresh or global error handling
// client.interceptors.response.use(...);
return { client };
};
Usage: Components or other hooks (like React Query hooks) would then use this useApiService
to get a pre-configured axios
client.
4.2. React Query Hooks (Data Fetching & Mutations)
As shown in Libraries and Their Common Uses, custom hooks are the standard way to wrap useQuery
and useMutation
calls from React Query.
Example: useFetchCurrentUser
(from previous doc)
// frontend/src/hooks/useUserData.js (Example)
import { useQuery } from '@tanstack/react-query';
import { useApiService } from '@/services/useApiService'; // Corrected path from example
const USER_QUERY_KEY = 'currentUser';
export const useFetchCurrentUser = (options) => {
const { client } = useApiService();
return useQuery({
queryKey: [USER_QUERY_KEY],
queryFn: async () => {
const response = await client.get('/users/me'); // Ensure endpoint exists
return response.data;
},
staleTime: 5 * 60 * 1000,
...options,
});
};
Example: useUpdateUserProfile
(from previous doc)
// frontend/src/hooks/useUserData.js (Continued)
import { useMutation, useQueryClient } from '@tanstack/react-query';
// import { useApiService } from '@/services/useApiService'; // Already imported
export const useUpdateUserProfile = (options) => {
const { client } = useApiService();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (userData) => {
const response = await client.put('/users/profile', userData); // Ensure endpoint exists
return response.data;
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
// Optionally: queryClient.setQueryData([USER_QUERY_KEY], data);
},
...options,
});
};
useComplexFormLogic
)
4.3. Form Handling Hooks (e.g., If a form has particularly complex stateful logic beyond what react-hook-form
handles directly, or involves multiple related state pieces specific to that form's domain, a custom hook can encapsulate this.
// frontend/src/hooks/useSpecificFormManagement.js (Conceptual)
import { useState, useCallback } from 'react';
export const useSpecificFormManagement = (initialValues) => {
const [customField, setCustomField] = useState(initialValues.customField || '');
// ... other specific form-related states ...
const handleCustomFieldChange = useCallback((value) => {
// ... custom logic ...
setCustomField(value);
}, []);
return {
customField,
handleCustomFieldChange,
// ... other exposed values and handlers ...
};
};
useModalState
, useToggle
)
4.4. UI Interaction Hooks (e.g., Simple hooks to manage common UI states like modal visibility or toggle states.
// frontend/src/hooks/useToggle.js
import { useState, useCallback } from 'react';
export const useToggle = (initialState = false) => {
const [isOn, setIsOn] = useState(initialState);
const toggle = useCallback(() => setIsOn(prev => !prev), []);
const setOn = useCallback(() => setIsOn(true), []);
const setOff = useCallback(() => setIsOn(false), []);
return { isOn, toggle, setOn, setOff };
};
useWindowSize
, useLocalStorage
)
4.5. Window/Browser API Hooks (e.g., Hooks to interact with browser APIs more declaratively.
// frontend/src/hooks/useWindowSize.js (Conceptual)
import { useState, useEffect } from 'react';
export const useWindowSize = () => {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => {
const handleResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
};
5. Guidelines for Creating Custom Hooks
- Single Responsibility: A custom hook should ideally do one thing well.
- Clear Interface: Expose a clear and minimal API (what it returns).
- Follow Rules of Hooks: Only call other Hooks at the top level of your custom hook. Don't call Hooks inside loops, conditions, or nested functions.
- Testability: Write unit tests for your custom hooks, especially those with complex logic.
- Documentation: Document what the hook does, its parameters, and what it returns, especially if it's intended for wide reuse.
Review the frontend/src/hooks/
directory to identify and document all significant custom hooks used in the LCFS project. Add specific examples and usage guidelines for each.