javascriptreactjsreact-query

React Not Rendering Fetched Posts from API Using React Query


I'm developing a React application that fetches posts (tweets) from an API using React Query, but I'm having trouble rendering the data after it's fetched.

Setup Overview:

Code Snippet:

Here’s a simplified version of my component:

const Posts = ({ feedType, username, userId, currentUserId }) => {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();

const getPostEndpoint = () => {
    // Logic to determine the endpoint based on feedType
};

const { isLoading, refetch, isRefetching, error } = useQuery({
    queryKey: ["posts", feedType, username, userId],
    queryFn: async () => {
        const token = localStorage.getItem('token');
        if (!token) {
            navigate('/');
            return [];
        }

        const response = await fetch(getPostEndpoint(), {
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type': 'application/json',
            }
        });

        if (!response.ok) throw new Error('Failed to fetch posts');
        const parsedData = await response.json();
        return parsedData.tweet_feed || [];
    },
    onSuccess: (data) => {
        setPosts(data);
    },
    enabled: !!username && !!localStorage.getItem('token'),
});

// Render logic
return (
    <>
        {(isLoading || isRefetching) && <PostSkeleton />}
        {!isLoading && !isRefetching && posts.length === 0 && <p>No posts in this tab.</p>}
        {!isLoading && !isRefetching && posts.length > 0 && (
            <div>
                {posts.map((post) => (
                    <Post key={post.id} post={post} currentUserId={currentUserId} />
                ))}
            </div>
        )}
        {error && <div>Error fetching posts: {error.message}</div>}
    </>
);

};

API Response:

I confirmed that the API is working as expected. Here's an example response from the /api/explore/ endpoint:

{
"tweets": [
    {
        "id": 2,
        "content": "amir",
        "creation_date": "2024-09-13"
    },
    {
        "id": 1,
        "content": "Hello!",
        "creation_date": "2024-09-13"
    }
]

}

Backend View (views.py): Here’s the relevant part of my Django backend that handles the request:

class GetTweetFeedView(viewsets.ViewSet):
permission_classes = [IsAuthenticated]

def list(self, request):
    profile = get_object_or_404(BasicUserProfile, user=request.user)
    followings = Follower.objects.filter(follower=profile)
    following_ids = [following.following.id for following in followings]
    tweets = Tweet.objects.filter(user_id__in=following_ids).order_by("-id")
    
    response_data = [
        {
            'id': tweet.id,
            'content': tweet.content,
            'creation_date': tweet.creation_date.strftime('%Y-%m-%d %H:%M:%S'),
            # Additional fields...
        }
        for tweet in tweets
    ]

    return Response({'tweet_feed': response_data}, status=200)

Console Output: In the console, I can see the fetched data logged correctly:

Fetched data: 
Object { tweets: (2) [...] }

Issues Encountered:

  1. Posts State Not Rendering: Despite seeing that the posts state is being set, the component does not render the posts.
  2. Empty Array: The posts state shows as an empty array when the component first renders, even though the API responds with data.

Questions:

  1. Why might the posts state not be rendering even though the data is fetched correctly?
  2. Is there anything in my use of React Query that could be causing this issue?
  3. Could there be an issue with how I'm accessing the data returned from the API?

Any help or insights would be greatly appreciated!


Solution

  • The issue of React not rendering fetched posts from the API was due to the backend API not returning data in the format that the React front end expected. Specifically, the frontend expected an array of posts, but the response structure from the backend was not aligned with this expectation. The key change was to ensure that the backend always returns data in the form of an array, as expected by the React frontend.

    Key Fixes:

    1. Ensure API Returns Data as Arrays: The backend API was updated to return an array of posts (or tweets) where each post is represented by an array containing multiple attributes. This ensures that the React frontend can process and render the posts correctly.

      In the corrected backend code, response_data is constructed as an array of tweet data, and each tweet is represented as an array with the necessary fields:

      response_data = [
          [
              tweet.id,
              tweet.content,
              tweet.image.url if tweet.image else None,
              tweet.video.url if tweet.video else None,
              tweet.creation_date.strftime('%Y-%m-%d %H:%M:%S'),
              [
                  tweet.user.id,
                  tweet.user.user.username,
                  tweet.user.full_name,
                  tweet.user.profile_photo.url if tweet.user.profile_photo else 'https://yookapp.ir/media/profile_photo/profile.jpg',
              ],
              "promoted" if tweet == selected_promoted_tweet else "regular",  # Mark the promoted tweet
              tweet.tweet_like_amount,  # Add the like count from the model field
              tweet.tweet_comment_amount,  # Add the comment count from the model field
              tweet.view_count  # Add the view count from the model field
          ]
          for tweet in combined_tweets
      ]
      

      This ensures that the data returned by the backend is an array, where each element is a list of tweet attributes that the frontend expects.

    2. Frontend Expectation of Arrays: In the frontend React code, the useQuery hook fetches posts and expects the backend to return the data as an array. If the response from the API is not in the expected format, React will not be able to render it properly. By ensuring that the backend returns an array of posts, React can then process and display the data accordingly.

    3. Frontend Code Example: The React component (Posts) utilizes useQuery to fetch posts. Since the backend now returns the data as an array, React can map over the postsWithPromoted array to render the posts:

      {postsWithPromoted.map((post) => (
          <Post
              key={post.id}
              post={{
                  id: post.id,
                  content: post.content,
                  image: post.image,
                  video: post.video,
                  creationDate: post.creationDate,
                  user: post.user,
                  comments: post.comments,
                  isPromoted: post.postType === "promoted", // Add a flag for promoted posts
              }}
          />
      ))}
      

    Backend Changes Overview:

    Summary:

    By adjusting the backend to always return an array of posts, React can now correctly render the posts from the API. This change was crucial because the React frontend expected data in array format, and without it, the posts could not be rendered. The backend is now properly serializing and sending the data as an array, ensuring seamless integration with the frontend.