State Management in React
In this section, we'll dive deep into state management for our PhotoSky application using React hooks. We'll cover how to manage local component state, share state between components, handle side effects, implement error handling, and manage loading states in our application.
Understanding State in React​
State in React represents the data that can change over time in your application. When state changes, React re-renders the components that depend on that state, updating the UI to reflect the new data.
React Hooks for State Management​
React Hooks are functions that let you "hook into" React state and lifecycle features from function components. We'll be using several hooks in our PhotoSky application:
- useState: For managing local component state
- useEffect: For performing side effects in function components
- useCallback: For memoizing functions to optimize performance
- useRef: For creating mutable references that persist across re-renders
Let's see how we're using these hooks in our PhotoSky application.
Managing Application State​
In our Album
component (inside App.js
), we're using several useState
hooks to manage our application state:
// the parameter being passed to useState is used as the default value
const [images, setImages] = useState([]);
const [selectedImage, setSelectedImage] = useState(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [navValue, setNavValue] = useState(0);
const [themeMode, setThemeMode] = useState('system');
const [isDarkMode, setIsDarkMode] = useState(false);
const [loading, setLoading] = useState(false);
const [imageDialogOpen, setImageDialogOpen] = useState(false);
const [menuAnchorEl, setMenuAnchorEl] = useState(null);
These state variables manage different aspects of our application:
images
: Stores the list of images fetched from the backendselectedImage
: Keeps track of the image selected for viewing or deletiondialogOpen
: Controls the visibility of the image viewer dialognavValue
: Manages the selected value in the bottom navigationthemeMode
: Stores the current theme mode ('light', 'dark', or 'system')isDarkMode
: A boolean indicating whether dark mode is activeloading
: Indicates whether a loading operation is in progressimageDialogOpen
: Controls the visibility of the image upload/capture dialogmenuAnchorEl
: Stores the anchor element for the options menu
Fetching Images and Error Handling​
We use the useCallback hook to memoize our fetchImages
function, and the useEffect hook to call it when the component mounts. We also implement error handling and loading state management:
const fetchImages = useCallback(async () => {
setLoading(true);
try {
// query api
const response = await axios.get(`${API_URL}/list-images`);
// set state to be the response
setImages(response.data.images);
enqueueSnackbar('Images loaded successfully', { variant: 'success' });
} catch (error) {
console.error('Error fetching images:', error);
enqueueSnackbar('Error fetching images', { variant: 'error' });
} finally {
setLoading(false);
}
}, [API_URL, enqueueSnackbar]);
useEffect(() => {
fetchImages();
}, [fetchImages]);
Note the use of setLoading(true)
at the start of the operation and setLoading(false)
in the finally
block. This ensures that the loading state is properly managed regardless of whether the operation succeeds or fails.
Managing Theme​
We use state to manage the theme and provide a function to toggle it:
const handleToggleThemeMode = () => {
if (themeMode === 'system') {
setThemeMode('light');
setIsDarkMode(false);
} else if (themeMode === 'light') {
setThemeMode('dark');
setIsDarkMode(true);
} else {
setThemeMode('system');
checkDarkMode();
}
};
const checkDarkMode = useCallback(() => {
const systemDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (themeMode === 'system') {
setIsDarkMode(systemDarkMode);
}
}, [themeMode]);
useEffect(() => {
checkDarkMode();
}, [themeMode, checkDarkMode]);
Error Handling and Notifications​
We use the notistack
library for displaying notifications to the user. The enqueueSnackbar function is used throughout the application to show success and error messages:
import { useSnackbar } from 'notistack';
function Album() {
const { enqueueSnackbar } = useSnackbar();
// ... other code ...
enqueueSnackbar('Success message', { variant: 'success' });
enqueueSnackbar('Error message', { variant: 'error' });
// ... other code ...
}
Loading State​
We use a loading
state variable to track when API calls are in progress. This is used to display a loading indicator:
{loading && <LinearProgress />}
Conclusion​
We've implemented comprehensive state management in our PhotoSky application using React hooks. We're using:
- useState for managing local state (images, theme, dialogs, etc.)
- useEffect for side effects (fetching images, checking dark mode)
- useCallback for memoizing functions (fetch images, upload, take picture)
- useRef for creating a reference to the file input element
We've also implemented error handling, loading states, and a notification system to provide a smooth user experience. By using environment variables, we've made our application configurable and easier to deploy to different environments.
This approach allows us to manage our application's state efficiently, ensuring that our UI stays in sync with our data and that we're not unnecessarily re-rendering components or recreating functions. It also provides a robust way to handle asynchronous operations and keep the user informed about the application's status.
In the next section, we'll focus on integrating our frontend with the backend API, implementing the full functionality of our PhotoSky application.