Search code examples
next.jsaxiosserver-side-renderingnext.js13app-router

How to fetch data once in Next.js 13 and pass it as a props


I'm having an optimization issue when I want to display dynamic title for the page and dynamic UI.

In my case I'm sending the same request 2 times - 1st time to generate metadata and 2nd time in order to pass it as a props to my UI component.

Previously, in Next.js with pages, I would solve it using getServerSideProps, but as long as this component anyways rendering on server, I didn't find any function; solution which would allow me to pass data from the server as a props for my functions (as it was when you were using pages router)

I use axios for querying data, initially I wanted to use RTK Query (and this problem would be solved because of caching), but for now RTK Query doesn't support React Server Components

My component.ts file

import MyComponent from '@/components/screens/screen/component';

import { getDataSSR } from '@/components/screens/screen/api/screen.ssr';
import { IData } from '@/components/screens/screen/types/data.type';

export async function generateMetadata({ params }: { params: { id: string } }) {
  const data = await getDataSSR({ id: Number(params?.id)}) as IData;

  return {
    title: `${data?.data?.details?.userProfile?.fullName}`,
  };
}

const ComponentPage = async ({ params }: { params: { id: string } }) => {
  const data = await getDataSSR({ id: Number(params?.id) }) as IData;
  
  return <MyComponent id={params.id} data={data} />;
};

export default ComponentPage;

I tried to ask chat-gpt 4 about this, but unfortunately, he didn't provide me any valuable and working solution.


Solution

  • Problem :

    I'm sending the same request 2 times - 1st time to generate metadata and 2nd time in order to pass it as a props to my UI component.

    Solution :

    • NextJS does caching when you use fetch, but when using external libraries you don't know its internal cache behavior.
    • You can use cache function provided by React, wrap your API Call function in cache function. (Read the 1st link given below)

    Here's a small code I made :

    Folder Structure :

    projectName
    ├── src
    │   └── app
    │       ├── api
    │       ├── comp
    │       │   └── User.js
    │       ├── favicon.ico
    │       ├── globals.css
    │       ├── layout.js
    │       └── user
    │           ├── page.js
    │           └── [id]
    │               └── page.js
    └── tailwind.config.js 
    

    Explaination :

    • I have made a page called Users, which shows all users.

    • I have made a component which takes data, & makes hyerlinks to open a specific user page (click on Terry's link, opens Terry's details page).

    • I have made a user details page, which calls a details api to show user details. This page has, generateMetadata which calls a GetUserData API & the page function also calls GetUserData API .

    • [id]\page.js is user details page

    • comp is component folder

    All Users Page projectName\src\app\user\page.js :

    import axios from "axios";
    import User from "../comp/User";
    
    async function GetAllUsers() {
        let { data } = await axios.get('https://dummyjson.com/users')
    
        return data
    
        // CHANGE ABOVE CODE ACCORDING TO AXIOS
    }
    // ABOVE API SIMULATES SERVER-SIDE DATA FETCHING
    
    
    export default async function Page() {
    
        let UserData = await GetAllUsers();
        // console.log("Data KEYS from API on Serverside : ", Object.keys(UserData));
        // THIS LOG WILL BE IN TERMINAL AS THIS PAGE IS SERVER-SIDE RENDERED
        return (
            <div>
                <h1>Users Page</h1>
                <User Data={UserData} />
                {/* COMPONENT */}
            </div>
        )
    }
    

    User Component : projectName\src\app\comp\User.js

    'use client'
    import Link from 'next/link'
    import React, { useState } from 'react'
    
    const User = ({ Data }) => {
        // console.log("Data.users", Data.users);
        // VISBILE IN BROWSER
    
        const [UserData, SetUserData] = useState(Data.users)
        return (
            <div>
                <h3>Client Component Gets data from Server as props, renders it below.</h3>
                <ol>
                    {
                        UserData.map((u, i) => (
                            <li key={i}>
                                <Link href={`/user/` + u.id} >{u.firstName}</Link>
                            </li>
                        ))
                    }
                </ol>
            </div>
        )
    }
    export default User;
    

    User details page projectName\src\app\user\[id]\page.js :

    import axios from 'axios';
    import React from 'react'
    import { cache } from 'react'
    
    const GetUserData = cache(async (ID) => {
        // console.log(ID);
        console.log("GetUserData HIT (cache) : ", new Date().toLocaleTimeString());
        let { data } = await axios.get('https://dummyjson.com/user/' + ID)
        return data
    
    })
    
    
    // WITHOUT ANY CACHING , THIS RUNS TWICE
    // UNCOMMENT BELOW CODE TO RUN & COMMENT ABOVE CODE
    
    // const GetUserData = async (ID) => {
    //     // console.log(ID);
    //    console.log("GetUserData HIT : ", new Date().toLocaleTimeString());
    
    //     let { data } = await axios.get('https://dummyjson.com/user/' + ID)
    //     return data
    //     // CHANGE ABOVE CODE ACCORDING TO AXIOS
    // }
    
    export async function generateMetadata({ params }) {
    
        let UserData = await GetUserData(params.id)
        return {
            title: UserData.firstName + " " + params.id,
        };
    }
    
    const UserDetailsPage = async ({ params }) => {
        let UserData = await GetUserData(params.id)
        return (
            <div>
                <h1>UserDetailsPage </h1>
                <p>ID:{params.id} </p>
                <p>FirstName : {UserData.firstName}</p>
                <p>LastName : {UserData.lastName}</p>
            </div>
        )
    }
    
    export default UserDetailsPage
    

    Output :

    • Goto http://localhost:3000/user you will see all users fetch from api.

    • Now click on anyone, it will take you to UserDetailsPage, http://localhost:3000/user/id_of_user see this page has Title + User ID (made using generateMetadata function) this page has api 2 calls 1st uses cache & 2nd doesn't.

    • you will see in terminal it cache wrapped api call executes only once. But if you comment this cache wrapped api call & uncomment the below api call you see it fires twice.

    Please Read :

    If you still have any doubts, leave a comment(i will update answer if required)