Search code examples
javascriptreactjsmern

Moving axios calls out of React Component


Is this an anti-pattern in React if I do this? How can I improve?

I have an EventItem Component and another helper file - EventHelper.js which handles all the axios calls for the said component. The functions declared in this helper file receives states and setStates from the EventItem component. The functions in this helper file make the HTTP calls and sets the state in EventItem component. Something like this:

EventItem.js (Component):

import {getPostComments} from "../Services/EventHelpers";

function EventItem(props) {
  const [eventComments, setEventComments] = useState([]);
  const [loadingComments, setLoadingComments] = useState(false);

  const getPostCommentsHandler = async (eventId) => {
    await getPostComments(
      eventId,
      setLoadingComments,
      eventComments,
      setEventComments
    );
  };
}

EventHelper.js:

export const getPostComments = async (
  eventId,
  setLoadingComments,
  eventComments,
  setEventComments
) => {
  try {
    setLoadingComments(true);
    const res = await axios.get(`/events/${eventId}/comments`);
    setLoadingComments(false);
    setEventComments(...[...eventComments, res.data.comments]);
  } catch (e) {}
};

Solution

  • Your way of writing this code is a valid way. For these kind of states and functions React recommends creating custom hooks.

    The following code allows you to move all your state needed for getting the post comments to a separate hook. This will result in a cleaner component and you can reuse this hook in a different component.

    export function usePostComments() {
      const [eventComments, setEventComments] = useState([]);
      const [isLoading, setIsLoading] = useState(false);
    
      const getPostComments = async (eventId) => {
        try {
          setIsLoading(true);
          const res = await axios.get(`/events/${eventId}/comments`);
    
          setEventComments((prevComments) => {
            return [...prevComments, res.data.comments];
            // or this below, not sure if you're comments data is a array
            // return [...prevComments, ...res.data.comments];
          });
        } catch (e) {
          console.error(e);
        } finally {
          setIsLoading(false);
        }
      };
    
      return {
        isLoading,
        eventComments,
        getPostComments,
      };
    }
    

    In the EventItem you could use it something like this, obviously very abstract but you'll get the idea.

    import { usePostComments } from "../hooks";
    
    function EventItem(props) {
      const { isLoading, eventComments, getPostComments } = usePostComments();
    
      return (
        <div>
          <button onClick={() => getPostComments("some-id")}>Get posts</button>
          {isLoading ? (
            <p>Sorry still loading</p>
          ) : (
            <div>
              {eventComments.map((comment) => {
                <p>{comment.stuff}</p>;
              })}
            </div>
          )}
        </div>
      );
    }