NewJS
NewJS

Reputation: 9

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.

Upvotes: 0

Views: 3921

Answers (2)

Daniel
Daniel

Reputation: 15393

It's important to understand that with NextJS you cannot use any kind of hook, otherwise you will get an error to the effect of:

ReactServerComponentsError:

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

Super clear error.

Additionally, your server components cannot assign any event handlers, otherwise you will get this error:

Unhandled Runtime Error Error: Event handlers cannot be passed to Client Component props.

It may also indicate exactly where you misused that event handler.

It's probably obvious by now but to make a client component you simply create the component and then at the top of the file you write out the following:

'use client';

When we create a client component in NextJS they do follow all the usual rules of using hooks, event handlers, etcetera, but they are limited in that they cannot directly show a Server Component. For example:

'use client';

import { useState } from 'react';
import ServerComponent from 'sdfghjk';

And then show it as a child directly like so:

'use client';

import { useState } from 'react';
import ServerComponent from 'sdfghjk';

export default function Color() {
  const [color, setColor] = useState('');

  return (
    <div>
     <ServerComponent />
     <input 
       value={color}
       onChange={e => setColor(e.target.value)}
     />
      {color}
    </div>
  );
}

The above would give us an error message.

So use client components when you need to use hooks and event handlers.

And remember client components get rendered one time on the server and so as much as possible, we really want to favor server components.

Upvotes: 0

Beast80K
Beast80K

Reputation: 1377

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)

Upvotes: 5

Related Questions