skip to Main Content

This function convertToUpperCase(item.name) runs 3-150 times. You can see it in context below.

In my test db, I have only 1 row of data in my db that I am returning so I am expecting it to run only 1 time.

// /app/Tacos/page.js
'use client';
import React, { useEffect, useState } from 'react';

//this runs 3-150 times (with only 1 row of data in the db).
const convertToUpperCase = (param) => {
    console.log('convertToUpperCase');
    return param.toUpperCase();
};

export default function Page() {
    const [myData, setMyData] = useState([]);
    var randomNum = 1;

    //this runs 1 time
    // this is calling a router handler
    const getData = async () => {
        const response = await fetch('/api/tacos', {
            method: 'GET',
        });

        if (response.ok) {
            console.log('Response.Ok');
            const { data } = await response.json();
            console.log(data);
            setMyData((prev) => data);
        } else {
            console.log('Error');
        }
    };

    // this runs 1 time
    useEffect(() => {
        console.log('useEffect');
        getData();
    }, []);

    // Experimented with the location of this function which did not make a difference
    // //this runs 3-150 times (with only 1 row of data in the db).
    // const convertToUpperCase = (param) => {
    //     console.log('convertToUpperCase');
    //     return param.toUpperCase();
    // };

    return (
        <div className='text-3xl text-white'>
            {myData.map((item, index) => (
                <div key={index}>{convertToUpperCase(item.name)}</div>
            ))}
        </div>
    );
}

Please make sure that React Strict Mode is explicitly turned off:

// ./next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
    reactStrictMode: false,
};

module.exports = nextConfig;

For the sake of the experiment, here is a very simple route handler that can be used:

import { NextRequest, NextResponse } from 'next/server';

export async function GET(req) {
    const data = [
        { name: 'taco' },
    ];
    return Response.json({ data });
}

This is running from Terminal locally.

I am seeing the counts in Visual Studio Code with a console logger.

I’m thinking through what the possibilities of this could be.

Obviously, the above is an miniature isolation of the problem but I’ll test to see if this issue appears even in this use case and then start adding more and more of the rest of the code on this page (or subtracting from the larger code base) until something pops but if the code ^ alone points to a recognizable pattern, I would appreciate it.

2

Answers


  1. Chosen as BEST ANSWER

    TLDR;

    1. Refreshing a running app will result in code flow / console log counts you expect
    2. Changing code will trigger a [Fast Refresh) rebuilding, and in this rebuilding process will run convertToUpperCase 3 times (sometimes 4) and the order of convertToUpperCase with respect will change from rebuild to rebuild
    3. If you look at console log in Chrome, the first console message after [Fast Refresh] rebuilding will be convertToUpperCase and the number of times that first one runs will be based on the previous build (e.g. it will use some cached value)
    4. Having multiple tabs open will increase the count you see in the console logger in VS Code (e.g. changes will actively be pushed to running instances regardless of whether you are actively looking at the tab in Chrome)

    End TLDR;


    I ran various experiments to see if I could replicate the issue. For the experiments, I am keep track of console logger in VSCode and cross referencing this in Chrome browser dev tools.

    The four console.log's I am keeping track of:

    console.log('convertToUpperCase');
    console.log('Response.Ok');
    console.log(data);
    console.log('useEffect');
    

    The scenarios:

    1. Scenario one. Suppose the app is already running locally in the Chrome. Press refresh in Chrome. Here is what your console log outputs and counts will look like in VSCode and Chrome.

    VSCode (in order it appears in code):

    convertToUpperCase - 1
    Response.Ok - 1
    [{...}] - 1
    useEffect - 1
    

    Chrome (in the order it appears in Console):

    useEffect - 1
    Response.Ok - 1
    [{...}] - 1
    convertToUpperCase - 1
    

    Looking at the Chrome dev tools, this is both the order and number I was expecting.

    1. Scenario Two. In order to better figure out the unexpected behavior, here's what I did. I added a random unused variable to the code, I called it var randomNum = 1;, this would reset the console counts in VSCode but also did something unexpected (outputted [Fast Refresh] rebuilding which helped pinpoint the issue.

    So, while the app is up in Chrome, running localhost in terminal there are the counts:

    VSCode (in order it appears in code):

    convertToUpperCase - 3
    Response.Ok - 1
    [{...}] - 1
    useEffect - 1
    

    Chrome (in the order it appears in Console):

    [Fast Refresh] rebuilding
    convertToUpperCase - 1
    useEffect - 1
    convertToUpperCase - 1
    Response.Ok - 1
    [{...}] - 1
    convertToUpperCase - 1
    

    This is unexpected, regardless of rebuild or refresh I was still expecting the number of times convertToUpperCase to follow code flow. However, this is good to know at least one source of where the issue was stemming from and I explained it away as saying, "Perhaps the rebuild runs the various functions and outputs the console a few extra times?" ... but this doesn't fully make sense as only convertToUpperCase is run more than the other ones.

    Then for completeness, ran this same experiment (changing the randomNum and saving to trigger the rebuild) a few more times. This is when it became even more confusing. The output sometimes changes to the following:

    VSCode (in order it appears in code): //this is the same

    ConvertToUpperCase - 3
    Response.Ok - 1
    [{...}] - 1
    useEffect - 1
    

    Chrome (in the order it appears in Console): // Notice that the order changes

    [Fast Refresh] rebuilding
    ConvertToUpperCase - 1
    useEffect - 1
    Response.Ok - 1
    [{...}] - 1
    ConvertToUpperCase - 1
    ConvertToUpperCase - 1
    

    When running this a few times, this second ^ output order shows up a bit more. And sometimes, there is even another output that appears less frequently where convertToUpperCase runs 4 times:

    [Fast Refresh] rebuilding
    convertToUpperCase - 1
    useEffect - 1
    Response.Ok - 1
    convertToUpperCase - 1
    [{...}] - 1
    convertToUpperCase - 1
    convertToUpperCase - 1
    

    So far, it seems there is something "fundamental" to the rebuilding process that makes the functions run in such a way that doesn't directly to what code flow would indicate.

    It has covertToUpperCase running more often. This is not a useEffect or state-related function but something that is tied closely to the outputting of the HTML so perhaps this is related to React's screen painting process and perhaps there is something about a previous build or a cached value that it is using? (And is this part of the normal comparison/creation of Virtual DOM on updates at play in the rebuilding process.)

    1. Scenario Three. While this so far has been pretty useful to understand why this function runs 3 or 4 times instead of just 1 time, it still does not account for the function running way more times (~150 times) than I expected.

    Is there something about having more data that makes this behavior unusual?

    Of course this is not just to observe "more rows of data leads x number of times function runs = a bigger number" but instead investigating wether something about having more data can lead to unexpected changes in behavior.

    So I increased the returned array from 1 item to 30 items.

    So the route handler became this:

    import { NextRequest, NextResponse } from 'next/server';
    
    export async function GET(req) {
        
        const data = [
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
            { name: 'taco' },
        ];
        return Response.json({ data });
    }
    

    I ran it a few times as well, and generally the results look like the following, and unfortunately didn't really learn anything new here. It was simply 30 times what I saw before:

    VSCode (in order it appears in code):

    convertToUpperCase - 90 // 3 * 30
    Response.Ok - 1
    [{...}] - 1
    useEffect - 1
    

    Chrome (in the order it appears in Console):

    [Fast Refresh] rebuilding
    convertToUpperCase - 30
    useEffect - 1
    convertToUpperCase - 30
    Response.Ok - 1
    [{...}] - 1
    convertToUpperCase - 30
    
    1. Scenario Four. On a whim, I decide to go down to 2 data points and in order to compare it to the x30 results, I opened this in a new tab. This is the second time things got interesting.

    Based on the previous results I was expecting something like this:

    VSCode (in order it appears in code):

    convertToUpperCase - 6 // 3 * 2
    Response.Ok - 1
    [{...}] - 1
    useEffect - 1
    

    But instead got something like this:

    VSCode (in order it appears in code):

    convertToUpperCase - 68 // !!!!????
    Response.Ok - 2
    [{...}] - 2
    useEffect - 2
    

    Okay so here how it gets interesting.

    Firstly, I was able to replicate "a big number without a simple explanation".

    Second, I realized that having multiple tabs open will sum the values of what's happening on these tabs. I was only actively looking at one tab at the time. My understanding was that React was driven from the client side primarily and that users navigating to the pages or at least having active tabs open would trigger various events like useEffect(()=>{...}, []), etc. but it seems this is not the case.

    Now this might only be applicable while running Terminal/localhost...perhaps this is NextJS running a listener that listens for changes in code and actively pushes these changes to each instance regardless of whether the tab is open. I'm not sure that this would be the same behavior if this is running from a remote server (ie. production or remote dev server.)

    Thirdly, breaking down the number 68 (which is the number of times convertToUpperCase ran) is the second interesting thing I saw. Normally you would expect 12 which would (2 + 2 + 2) * 2 for each tab. Looking in Chrome I saw the following.

    Chrome (in the order it appears in Console):

    [Fast Refresh] rebuilding
    convertToUpperCase - 30 // !!! So apparently something of the previous build gets cached!!!
    useEffect - 1
    Response.Ok - 1
    [{...}] - 1
    convertToUpperCase - 2
    convertToUpperCase - 2
    

    And then in the next rebuild it shows up like this: Chrome (in the order it appears in Console):

    [Fast Refresh] rebuilding
    convertToUpperCase - 2
    useEffect - 1
    convertToUpperCase - 2
    Response.Ok - 1
    [{...}] - 1
    convertToUpperCase - 2
    

    So to summarize where that 68 number came from VSCode (in order it appears in code):

    convertToUpperCase - 68 // (30 + 2 + 2) + (30 + 2 + 2)
    Response.Ok - 2
    [{...}] - 2
    useEffect - 2
    

    So the bottom line is this:

    1. Refreshing a running app will result in code flow / console log counts you expect
    2. Changing code will trigger a [Fast Refresh) rebuilding, and in this rebuilding process will run convertToUpperCase 3 times (sometimes 4) and the order of convertToUpperCase with respect will change from rebuild to rebuild
    3. If you look at console log in Chrome, the first console message after [Fast Refresh] rebuilding will be convertToUpperCase and the number of times that first one runs will be based on the previous build (e.g. it will use some cached value)
    4. Having multiple tabs open will increase the count you see in the console logger in VS Code (e.g. changes will actively be pushed to running instances regardless of whether you are actively looking at the tab in Chrome)

  2. You are using the convertToUpperCase function inside the map function, and the map is based on myData array, so convertToUpperCase will run for each element of myData.

    The length of myData will determine how many times it will run. Otherwise what you do is, inside the getData function you can map through response data and make it uppercase.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search