Skip to content

bug: [V4] Uncaught Error: Class extends value [object Object] is not a constructor or null when importing import { useRealtimeRun } from '@trigger.dev/react-hooks'; in nextjs #2211

@cachho

Description

@cachho

Provide environment information

  System:
    OS: Linux 6.6 Ubuntu 20.04.6 LTS (Focal Fossa)
    CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
    Memory: 10.45 GB / 15.58 GB
    Container: Yes
    Shell: 3.7.1 - /usr/bin/fish
  Binaries:
    Node: 18.20.8 - ~/.local/share/nvm/v18.20.8/bin/node
    npm: 10.8.2 - ~/.local/share/nvm/v18.20.8/bin/npm
    pnpm: 8.6.3 - ~/.local/share/pnpm/pnpm

Describe the bug

Uncaught Error: Class extends value [object Object] is not a constructor or null

Reproduction repo

not at this point

To reproduce

important to know that this happens simply by importing the package, it's not triggered.
This is in the parent component ('use client'). runId and publicAccesstoken are null.

      {runId && publicAccessToken ? (
        <DecryptionLoadingDialog
          runId={runId}
          publicAccessToken={publicAccessToken}
          callback={props.callback}
        />
      ) : null}

Does not work:

import { useRealtimeRun } from '@trigger.dev/react-hooks';
import { LoaderCircle } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import type { decryptLink } from '@/trigger/decrypt-link';

import type { OpenLinkOptions } from '../types/OpenLinkOptions';

type Props = {
  runId: string;
  publicAccessToken: string;
  callback: (options: OpenLinkOptions) => void;
};

export const DecryptionLoadingDialog = ({
  runId,
  publicAccessToken,
  callback,
}: Props) => {
  const router = useRouter();
  const [open, setOpen] = useState(true);
  const [isLoading, setIsLoading] = useState(true);
  const [attempt, setAttempt] = useState(1);
  const { run, error } = useRealtimeRun<typeof decryptLink>(runId, {
    accessToken: publicAccessToken,
  });

  useEffect(() => {
    if (!run) return;
    console.log('🚀 ~ run:', run);
    if (run.status === 'QUEUED') {
      setIsLoading(true);
    }
    if (run.status === 'REATTEMPTING') {
      setAttempt((prev) => prev + 1);
    }
    if (run.status === 'FAILED' || run.status === 'COMPLETED') {
      setIsLoading(false);
    }
    if (run.status === 'COMPLETED' && run.output) {
      callback({
        url: new URL(run.output),
        isRandom: false,
        router,
      });
      setIsLoading(false);
      setOpen(false);
    }
  }, [run?.status]);

  useEffect(() => {
    // Reset
    setIsLoading(false);
    setOpen(true);
    setAttempt(1);
  }, [runId, publicAccessToken]);

  function constructText() {
    if (!run) {
      return 'Decrypting link...';
    }
    if (error) {
      return 'Decryption submission error.';
    }
    if (run?.status === 'FAILED') {
      return 'Link cannot not be decrypted.';
    }
    if (run?.status === 'COMPLETED') {
      return 'Link decrypted successfully.';
    }
    return `Attempt ${attempt} of 3`;
  }

  return (
    <Dialog onOpenChange={(o) => setOpen(o)} open={open}>
      <DialogContent className="max-w-[75%] lg:max-w-[50%]">
        <DialogHeader>
          <DialogTitle>Link Decryption</DialogTitle>
          <DialogDescription>
            Please wait while we decrypt the link. This may take a few seconds.
          </DialogDescription>
        </DialogHeader>
        {isLoading && (
          <div className="w-full">
            <LoaderCircle className="mx-auto size-24 animate-spin" />
          </div>
        )}
        <Input
          disabled
          value={constructText()}
          className={twMerge(
            error || run?.status === 'FAILED'
              ? 'text-destructive-foreground bg-destructive'
              : '',
            'disabled:opacity-100'
          )}
        />
        <DialogFooter>
          <Button variant="destructive" onClick={() => setOpen(false)}>
            Cancel
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};

works (do not import ``)

import { LoaderCircle } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';

import type { OpenLinkOptions } from '../types/OpenLinkOptions';

type Props = {
  runId: string;
  publicAccessToken: string;
  callback: (options: OpenLinkOptions) => void;
};

export const DecryptionLoadingDialog = ({
  runId,
  publicAccessToken,
  callback,
}: Props) => {
  const router = useRouter();
  const [open, setOpen] = useState(true);
  const [isLoading, setIsLoading] = useState(true);
  const [attempt, setAttempt] = useState(1);
  const { run, error } = {
    run: {
      status: 'QUEUED',
      output: 'https://www.google.com',
    },
    error: null,
  }; // Replace with actual data fetching logic

  useEffect(() => {
    if (!run) return;
    console.log('🚀 ~ run:', run);
    if (run.status === 'QUEUED') {
      setIsLoading(true);
    }
    if (run.status === 'REATTEMPTING') {
      setAttempt((prev) => prev + 1);
    }
    if (run.status === 'FAILED' || run.status === 'COMPLETED') {
      setIsLoading(false);
    }
    if (run.status === 'COMPLETED' && run.output) {
      try {
        callback({
          url: new URL(run.output),
          isRandom: false,
          router,
        });
      } catch (e) {
        console.error('Error parsing output URL:', e);
      }
      setIsLoading(false);
      setOpen(false);
    }
  }, [run?.status]);

  useEffect(() => {
    // Reset
    setIsLoading(false);
    setOpen(true);
    setAttempt(1);
  }, [runId, publicAccessToken]);

  function constructText() {
    if (!run) {
      return 'Decrypting link...';
    }
    if (error) {
      return 'Decryption submission error.';
    }
    if (run?.status === 'FAILED') {
      return 'Link cannot not be decrypted.';
    }
    if (run?.status === 'COMPLETED') {
      return 'Link decrypted successfully.';
    }
    return `Attempt ${attempt} of 3`;
  }

  return (
    <Dialog onOpenChange={(o) => setOpen(o)} open={open}>
      <DialogContent className="max-w-[75%] lg:max-w-[50%]">
        <DialogHeader>
          <DialogTitle>Link Decryption</DialogTitle>
          <DialogDescription>
            Please wait while we decrypt the link. This may take a few seconds.
          </DialogDescription>
        </DialogHeader>
        {isLoading && (
          <div className="w-full">
            <LoaderCircle className="mx-auto size-24 animate-spin" />
          </div>
        )}
        <Input
          disabled
          value={constructText()}
          className={twMerge(
            error || run?.status === 'FAILED'
              ? 'text-destructive-foreground bg-destructive'
              : '',
            'disabled:opacity-100'
          )}
        />
        <DialogFooter>
          <Button variant="destructive" onClick={() => setOpen(false)}>
            Cancel
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};

Additional information

using: 4.0.0-v4-beta.21
nextjs version: 14.2.30

error:

Uncaught Error: Class extends value [object Object] is not a constructor or null
    React 15
    workLoop webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js:256
    flushWork webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js:225
    performWorkUntilDeadline webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js:534
    EventHandlerNonNull* webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js:569
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js:630
    NextJS 4
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/scheduler/index.js:4
    NextJS 4
    React 2
    NextJS 4
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/index.js:32
    NextJS 4
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/client.js:3
    NextJS 4
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:15
    NextJS 4
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-next-dev.js:9
    appBootstrap webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-bootstrap.js:57
    loadScriptsInSequence webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-bootstrap.js:23
    appBootstrap webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-bootstrap.js:56
    <anonymous> webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-next-dev.js:8
    NextJS 9
react-dom.development.js:17497:1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions