javascripttypescriptnext.jsreact-hooks

useeffect in nextjs 14 firing twice at page load


I have a client component in next js which uses useeffect to call an api, however its is calling twice at page load the api, i dont know why. here is the client component:

'use client';

import { useState, useEffect } from 'react';
import { useInView } from 'react-intersection-observer';

import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons';
import { cn } from '@/app/lib/utils';
import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useDebouncedCallback } from 'use-debounce';

let offset = 0;

export function ComboboxDemo({ services }: { services: string[] }) {
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState('');

  const searchParams = useSearchParams();
  const initialQuery = searchParams.get('query_service') || '';
  const [inputValue, setInputValue] = useState(initialQuery);

  const { replace } = useRouter();
  const pathname = usePathname();

  const { ref, inView } = useInView();
  const [data, setData] = useState<string[]>([]);

  useEffect(() => {
    if (inView) {
      const fetchService = async () => {
        const response = await fetch(
          `/dashboard/inventory/api/services?offset=${offset}`,
        );
        const result = await response.json();
        return result;
      };
      fetchService().then((res) => {
        setData([...data, ...res]);
      });
    }
    console.log('i fire once');
  }, [inView, data]);

and here is the api endpoint:

import { db } from '@/drizzle/db';
import { evergreen } from '@/drizzle/schema';
import { ilike, sql } from 'drizzle-orm';
import { NextRequest, NextResponse } from 'next/server';

const limit = 100;

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const offset = searchParams.get('offset') || 0;

  const dataPromise = db
    .selectDistinct({
      it_service: evergreen.it_service,
    })
    .from(evergreen)
    .offset(sql.placeholder('offset'))
    .limit(limit)
    .prepare('distinct_it');

  const data = await dataPromise.execute({ offset: offset });
  console.log(data);

  const services = data.map((item) => item.it_service);

  return NextResponse.json(services, { status: 200 });
}

P.S: I am not in Strict Mode.

Another thing to note is that the API endoint is a route handler, not a server action.


Solution

  • It would seem you have added a dependency to the useEffect hook that the callback updates. This will trigger the effect to run again.

    useEffect(() => {
      if (inView) {
        const fetchService = async () => {
          const response = await fetch(
            `/dashboard/inventory/api/services?offset=${offset}`,
          );
          const result = await response.json();
          return result;
        };
        fetchService().then((res) => {
          setData([...data, ...res]); // <-- updates data state
        });
      }
      console.log('i fire once');
    }, [inView, data]); // <-- data state is dependency
    

    Remove data as an external dependency by using a functional state update, i.e. using an updater function that is passed the current state value in order to compute and return the next state value.

    Example:

    useEffect(() => {
      if (inView) {
        const fetchService = async () => {
          const response = await fetch(
            `/dashboard/inventory/api/services?offset=${offset}`,
          );
          const result = await response.json();
          setData(data => [...data, ...result]);
          // or setData(data => data.concat(...result));
        };
    
        fetchService();
      }
    }, [inView]);