Search code examples
reactjstypescriptnext.jsserver-side-renderingcsr

How can I fetch data and display it in NextJs 13 or above?


I am new to NextJS so the question might sound a trivial so please bear with me. So, basically I want to fetch data from a database and display it in the page at the first render and for that I am trying to use useEffect and useState hooks as follows.

"use client"
import axios from "axios"
import Link from "next/link"
import { useEffect } from "react"
async function ProductsComponent( ) {
    const [products,setProducts] = useState<any[]>([])
    useEffect(()=>{
        async function fetchProducts() {
             "use server"
            try {
                const productsresponse = await axios.get(`${process.env.BASE_URL}/api/productControllers`)
                if(productsresponse.status === 200){
                    setProducts(productsresponse.data.message)
                }else{
                    console.log("Failed to fetch Products")
                }
            } catch (error) {
                
            }
        }
        fetchProducts()
    },[])
    return (
      <div className=" flex flex-col">
        <h3>Product Name</h3>
        {
            products?.map(product=>(
                <div>
                    <h4>{product?.productName}</h4>
                 </div>
                </div>
            ))
        }
      </div>
    )
  }
  
  export default ProductsComponent

but I get an error of:

async/await is not yet supported in Client Components, only Server Components.

and if i remove the "use client", I get an error of:

You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

So how can I ever fetch data from database and render it in NextJS >=13 ?

I tried defining a separate server side function component that fetches data from database using useEffect and the compoentnt takes the product and setproduct as props and I passed the component in anohter client component where we define states like product ,setProduct but I am still getting an error.


Solution

  • There are two ways to render a page:

    • Client Side - client gets page & it makes api call & then sets data in page.

    • Server side - server gets data & generates page & then sends it to the client.

      Two solutions :

    • Client side rendering, make a component with 'use client' at top of it & import it in your page.

    • Server side rendering (make page on server & then send to client)

    Read these concepts, for more clarity:

    As you said

    display it in the page at the first render

    So SSR would be the way to go, as it will generate page & send it to the client. No Loading states, will be seen.

    I'm using NextJS version : 13.5.4

    By default Next.js considers a component as Server Component. But when you use use client in a component you make it as client component.

    https://nextjs.org/docs/app/building-your-application/rendering/server-components#using-server-components-in-nextjs

    Hence it throws a error when u remove 'use client' from a component which uses (useEffect,useState etc. as this are hydrated/calculated on client side).

    You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

    Here is code with both implementations.

    Folder Structure :

    projectName
    ├── .gitignore
    ├── jsconfig.json
    ├── next.config.js
    ├── package-lock.json
    ├── package.json
    ├── postcss.config.js
    ├── public
    │   ├── images
    │   ├── next.svg
    │   └── vercel.svg
    ├── README.md
    ├── src
    │   └── app
    │       ├── api
    │       ├── comp
    │       │   └── ProductsList.js
    │       ├── favicon.ico
    │       ├── globals.css
    │       ├── layout.js
    │       ├── page.js
    │       ├── products
    │       │   └── page.js
    │       └── products_clientside
    │           └── page.js
    └── tailwind.config.js
    
    • Server side rendering

    loc\src\app\products\page.js

    import axios from "axios";
    
    async function GetProducts() {
        let { data } = await axios.get('https://dummyjson.com/products')
        return data
    }
    
    export default async function ProductsPage() {
    
    
        let Products = await GetProducts();
        console.log("Data from API on Serverside :", Object.keys(Products));
        // console.log("Products on Serverside :", Products.products);
    
        return (
            <div>
                <h1>Products Page (Serverside Fetching)</h1>
                {
                    Products
    
                        ?
                        Products.products.map((p, i) => {
                            return (
                                <p key={i}>{p.title}</p>
                            )
                        })
                        :
    
                        "Getting Products ...."
    
    
                    // THIS LOADING STATE WILL NOT BE VISIBLE BECAUSE SERVER LOADS THIS WHOLE PAGE
                }
            </div>
        )
    }
    

    Explaination :

    • Server when renders this page, GetProducts() is called on server-side, server waits, gets data & generates the whole page & then finally sends to client.
    • Client during this time of rendering, just sees a blank screen, loading indicator in browser.

    Output :

    • Go on route http://localhost:3000/products
    • you will see data console.log() in terminal (there 2 console logs 1 is to show keys present in data, other is commented out )

    -----------------------------------------------------------------------------

    Client side rendering:

    • Make a component in comp folder.

    ProductsList Component loc\src\app\comp\ProductsList.js

    'use client'
    import axios from 'axios'
    import React, { useEffect, useState } from 'react'
    
    const ProductsList = () => {
    
        async function GetProducts() {
            let { data } = await axios.get('https://dummyjson.com/products')
            console.log(data);
            SetProducts(data)
        }
    
        const [Products, SetProducts] = useState(null)
    
        useEffect(() => {
            GetProducts()
        }, [])
    
        return (
            <div>
                <h1>Products Page</h1>
                {
                    Products
    
                        ?
                        Products.products.map((p, i) => {
                            return (
                                <p key={i}>{p.title}</p>
                            )
                        })
                        :
    
                        "Getting Products ...."
    
                    // THIS LOADING STATE WILL  BE VISIBLE BECAUSE CLIENT LOADS A SECTION OF PAGE 
    
                }
            </div>
        )
    }
    
    export default ProductsList
    

    Now make a page loc\src\app\products_clientside\page.js & import ProductsList

    import ProductsList from "../comp/ProductsList"
    const page = () => {
        return (
            <div>
                <h1> Product Page Other Way (Client Side Fetching)</h1>
                <ProductsList />
            </div>
        )
    }
    export default page
    

    Explaination :

    • Client gets the page, then useEffect runs & calls API, shows Getting Products .... & when it gets data sets it & shows.

    Output :

    • Go to url http://localhost:3000/products_clientside

    • You will see "Getting Products ...." & also in console the data recieved from api

    • Go in the Network tab in present beside the console, open this page, you will see products_clientside under Name, click on it & then on right hand side click on Preview Tab you will see page is rendered till

    • Product Page Other Way (Client Side Fetching) Products Page Getting Products .... After that it Products state is set & rendered by client( client side rendering - browser does it)

    If you have any doubts, then please leave a comment (I will update answer if necessary)