Sifiya / ua-urban

https://ua-urban.vercel.app
MIT License
12 stars 2 forks source link

Does it make sense to use react query in the next instead of react server components? #29

Open allohamora opened 4 months ago

allohamora commented 4 months ago

With the current react-query approach, we need to load data on the client and see the Loader component and layout shifts:

https://github.com/Sifiya/ua-urban/assets/54174661/59207e83-a7d3-47a1-8d84-fa585952aea7

With react server components otherwise, we can make await for the data in components with "use server" and have the better user experience:

https://github.com/Sifiya/ua-urban/assets/54174661/05103a91-86c1-4fae-89c9-42fffd992a94

allohamora commented 4 months ago

Refactor example

src/features/AlphabetList/AlphabetList.tsx

From:

'use client';

import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { getAllWords } from '@/app/api/actions';
import { createAlphabet } from './utils';

import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Loader } from '@/components/Loader';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

interface AlphabetListProps {}

export const AlphabetList = ({}: AlphabetListProps) => {
  const { data = [], isLoading } = useQuery({
    queryKey: ['words'],
    queryFn: () => getAllWords(),
  });
  const alphabet = createAlphabet(data);

  if (isLoading) {
    return <Loader />;
  }

  if (!alphabet.length) {
    return null;
  }

  return (
    <Tabs defaultValue={`${alphabet[0][0]}-tab`}>
      <TabsList className="flex flex-wrap h-fit">
        {alphabet.map(([letter]) => (
          <TabsTrigger key={`${letter}-letter-tabname`} value={`${letter}-tab`}>
            {letter}
          </TabsTrigger>
        ))}
      </TabsList>
      {alphabet.map(([letter, words]) => (
        <TabsContent key={`${letter}-tab-content`} value={`${letter}-tab`}>
          <ul className="flex flex-wrap">
            {words.map((word) => (
              <li key={word.id} className="w-fit">
                <Link href={`/word/${word.id}`}>
                  <Button variant="link">
                    {word.word}
                  </Button>
                </Link>
              </li>
            ))}
          </ul>
        </TabsContent>
      ))}
    </Tabs>
  );
};

To:

'use server';

import React from 'react';
import { getWordDefinitions } from '@/app/api/actions';
import { Card, CardContent } from '@/components/ui/card';
import { VoteBlock } from './VoteBlock';
import { Paragraph } from '@/components/typography';

interface DefinitionsListProps {
  wordId: string;
}

export const DefinitionsList = async ({ wordId }: DefinitionsListProps) => {
  const definitions = await getWordDefinitions(wordId);

  const formattedDefinitions = definitions.map((def) => ({
    ...def,
    rating: def.upvotes_count - def.downvotes_count,
  }));
  const sortedDefinitions = formattedDefinitions.sort((a, b) => b.rating - a.rating);

  return sortedDefinitions.map(({ id, text, upvotes_count, downvotes_count }) => (
    <Card key={id} className="py-3 px-5">
      <CardContent>
        <Paragraph>{text}</Paragraph>
        <VoteBlock 
          definitionId={id}
          wordId={wordId}
          upvotes={upvotes_count}
          downvotes={downvotes_count}
        />
      </CardContent>
    </Card>
  ));
};