...
/Adding Read-Write Functionality to the ImageListAtom Function
Adding Read-Write Functionality to the ImageListAtom Function
Learn how to implement a like button using Jotai atoms, use read-only, write-only, and read-write atoms, handle asynchronous data fetching, and update UI components based on state changes.
The write functionality in imageListAtom
So far, we have a read-only imageListAtom
atom and an async fetchImagesAtom
atom. Let’s add a write functionality to imageListAtom
atom so that it can accept values from fetchImagesAtom
atom:
// src/atoms/fetchImageAtoms.js// Define an atom for an image list with an updater function.export const imageListAtom = atom([], (get, set, newArray) => {// Update the value of the atom with the new array.set(imageListAtom, newArray);});
The atom is ready to receive values, so let’s give it some. We have to go back to the Home
component where we kicked off data fetching and add a useEffect
, which will update imageListAtom
atom. Here’s what the code should look like:
// src/surfaces/Home.js// Import the necessary atom hooks.import { useAtom } from "jotai";import { fetchImagesAtom, imageListAtom } from "../atoms/fetchImageAtoms";// Define the Home component.export const Home = () => {// Use the fetchImagesAtom to get the JSON data.const [json] = useAtom(fetchImagesAtom);// Use the imageListAtom and setAllImages updater function to update the image list.const [, setAllImages] = useAtom(imageListAtom);// useEffect to update the image list when json data changes.useEffect(() => {if (json) {// Set the image list to the fetched JSON data.setAllImages(json);}}, [json]);};
Time to check
This is a good moment to check again whether everything works fine in the app since we just implemented data fetching. If everything is, in fact, working as expected, we’ll move on to implementing functionality for the “Like” button. If we run into any issues, we can start by using console.log
to check that the atoms hold and return the values we are expecting them to have.
import React, { useState, useEffect, useReducer } from "react"; import { requestBase } from "./utils/constants"; const ConversationContext = React.createContext(); function ConversationContextProvider({ children }) { const [conversationId, setConversationId] = useState(null); return ( <ConversationContext.Provider value={{ conversationId, setConversationId, }} > {children} </ConversationContext.Provider> ); } function useConversations() { const context = React.useContext(ConversationContext); if (context === undefined) { throw new Error( "useConversations must be used within a ConversationContextProvider" ); } return context; } export { ConversationContextProvider, useConversations }; export const UserStateContext = React.createContext(); export function useUserState() { const context = React.useContext(UserStateContext); if (context === undefined) { throw new Error( "useUserState must be used within a UserStateContextProvider" ); } return context; } const FavoritedContext = React.createContext(); function favoritesReducer(state, action) { switch (action.type) { case "init_likes": { return action.payload; } case "add_like": { const newLikedImage = action.payload; return [...state, newLikedImage]; } case "remove_like": { const stateWithoutLikedImage = state.filter( (item) => item !== action.payload ); return stateWithoutLikedImage; } default: { throw new Error(`Unhandled action type: ${action.type}`); } } } function FavoritedContextProvider({ children }) { const [loggedInData, setLoggedInData] = useState(null); const [state, dispatch] = useReducer(favoritesReducer, loggedInData); async function fetchLoggedInData() { const response = await fetch(requestBase + "/john_doe/likedImages.json"); setLoggedInData(await response.json()); } useEffect(() => { if (!loggedInData) { fetchLoggedInData(); } else { dispatch({ type: "init_likes", payload: loggedInData }); } }, [loggedInData]); const value = { state, dispatch }; return ( <FavoritedContext.Provider value={value}> {children} </FavoritedContext.Provider> ); } function useFavorited(userLoggedIn) { let context; if (userLoggedIn) { context = React.useContext(FavoritedContext); } if (context === undefined) { throw new Error( "useFavorited must be used within a FavoritedContextProvider" ); } return context; } export { FavoritedContextProvider, useFavorited }; const BookmarksContext = React.createContext(); function BookmarksContextProvider({ children }) { const [bookmarksData, setBookmarksData] = useState(null); async function fetchBookmarkData() { const response = await fetch( requestBase + "/john_doe/bookmarkedImages.json" ); setBookmarksData(await response.json()); } useEffect(() => { if (!bookmarksData) { fetchBookmarkData(); } }, [bookmarksData]); return ( <BookmarksContext.Provider value={{ bookmarksData, setBookmarksData, }} > {children} </BookmarksContext.Provider> ); } function useBookmarks() { const context = React.useContext(BookmarksContext); if (context === undefined) { throw new Error( "useBookmarks must be used within a BookmarksContextProvider" ); } return context; } export { BookmarksContextProvider, useBookmarks };
Once we are sure that everything is good, we’ll move on to implementing the “Like” button in ImageDetailsModal
.
Implementing the “Like” button
The full functionality of the “Like” button in ImageDetailsModal
consists of two ...