next.js

'use' is not exported from 'react' (imported as 'use')


I get this error when I build my nextjs app:

'use' is not exported from 'react' (imported as 'use')

I believe the error comes from the file, './node_modules/next-view-transitions/dist/index.js'. My app uses next-view-transitions and I imported the Link component from next-view transitions in my EventCard and EventContainer components. I browsed to the file in node_modules and here's the code:

'use client';
import { jsx } from 'react/jsx-runtime';
import NextLink from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
import { useSyncExternalStore, useRef, useState, useEffect, use, createContext, useCallback, startTransition, useMemo } from 'react';

function useHash() {
    return useSyncExternalStore(subscribeHash, getHashSnapshot, getServerHashSnapshot);
}
function getHashSnapshot() {
    return window.location.hash;
}
function getServerHashSnapshot() {
    return '';
}
function subscribeHash(onStoreChange) {
    window.addEventListener('hashchange', onStoreChange);
    return ()=>window.removeEventListener('hashchange', onStoreChange);
}

// TODO: This implementation might not be complete when there are nested
// Suspense boundaries during a route transition. But it should work fine for
// the most common use cases.
function useBrowserNativeTransitions() {
    const pathname = usePathname();
    const currentPathname = useRef(pathname);
    // This is a global state to keep track of the view transition state.
    const [currentViewTransition, setCurrentViewTransition] = useState(null);
    useEffect(()=>{
        if (!('startViewTransition' in document)) {
            return ()=>{};
        }
        const onPopState = ()=>{
            let pendingViewTransitionResolve;
            const pendingViewTransition = new Promise((resolve)=>{
                pendingViewTransitionResolve = resolve;
            });
            const pendingStartViewTransition = new Promise((resolve)=>{
                // @ts-ignore
                document.startViewTransition(()=>{
                    resolve();
                    return pendingViewTransition;
                });
            });
            setCurrentViewTransition([
                pendingStartViewTransition,
                pendingViewTransitionResolve
            ]);
        };
        window.addEventListener('popstate', onPopState);
        return ()=>{
            window.removeEventListener('popstate', onPopState);
        };
    }, []);
    if (currentViewTransition && currentPathname.current !== pathname) {
        // Whenever the pathname changes, we block the rendering of the new route
        // until the view transition is started (i.e. DOM screenshotted).
        use(currentViewTransition[0]);
    }
    // Keep the transition reference up-to-date.
    const transitionRef = useRef(currentViewTransition);
    useEffect(()=>{
        transitionRef.current = currentViewTransition;
    }, [
        currentViewTransition
    ]);
    const hash = useHash();
    useEffect(()=>{
        // When the new route component is actually mounted, we finish the view
        // transition.
        currentPathname.current = pathname;
        if (transitionRef.current) {
            transitionRef.current[1]();
            transitionRef.current = null;
        }
    }, [
        hash,
        pathname
    ]);
}

const ViewTransitionsContext = /*#__PURE__*/ createContext(()=>()=>{});
function ViewTransitions({ children }) {
    const [finishViewTransition, setFinishViewTransition] = useState(null);
    useEffect(()=>{
        if (finishViewTransition) {
            finishViewTransition();
            setFinishViewTransition(null);
        }
    }, [
        finishViewTransition
    ]);
    useBrowserNativeTransitions();
    return /*#__PURE__*/ jsx(ViewTransitionsContext.Provider, {
        value: setFinishViewTransition,
        children: children
    });
}
function useSetFinishViewTransition() {
    return use(ViewTransitionsContext);
}

function _extends$1() {
    _extends$1 = Object.assign || function(target) {
        for(var i = 1; i < arguments.length; i++){
            var source = arguments[i];
            for(var key in source){
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    target[key] = source[key];
                }
            }
        }
        return target;
    };
    return _extends$1.apply(this, arguments);
}
function _object_without_properties_loose(source, excluded) {
    if (source == null) return {};
    var target = {};
    var sourceKeys = Object.keys(source);
    var key, i;
    for(i = 0; i < sourceKeys.length; i++){
        key = sourceKeys[i];
        if (excluded.indexOf(key) >= 0) continue;
        target[key] = source[key];
    }
    return target;
}
function useTransitionRouter() {
    const router = useRouter();
    const finishViewTransition = useSetFinishViewTransition();
    const triggerTransition = useCallback((cb, { onTransitionReady } = {})=>{
        if ('startViewTransition' in document) {
            // @ts-ignore
            const transition = document.startViewTransition(()=>new Promise((resolve)=>{
                    startTransition(()=>{
                        cb();
                        finishViewTransition(()=>resolve);
                    });
                }));
            if (onTransitionReady) {
                transition.ready.then(onTransitionReady);
            }
        } else {
            return cb();
        }
    }, []);
    const push = useCallback((href, _param = {})=>{
        var { onTransitionReady } = _param, options = _object_without_properties_loose(_param, [
            "onTransitionReady"
        ]);
        triggerTransition(()=>router.push(href, options), {
            onTransitionReady
        });
    }, [
        triggerTransition,
        router
    ]);
    const replace = useCallback((href, _param = {})=>{
        var { onTransitionReady } = _param, options = _object_without_properties_loose(_param, [
            "onTransitionReady"
        ]);
        triggerTransition(()=>router.replace(href, options), {
            onTransitionReady
        });
    }, [
        triggerTransition,
        router
    ]);
    return useMemo(()=>_extends$1({}, router, {
            push,
            replace
        }), [
        push,
        replace,
        router
    ]);
}

function _extends() {
    _extends = Object.assign || function(target) {
        for(var i = 1; i < arguments.length; i++){
            var source = arguments[i];
            for(var key in source){
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    target[key] = source[key];
                }
            }
        }
        return target;
    };
    return _extends.apply(this, arguments);
}
// copied from https://github.com/vercel/next.js/blob/66f8ffaa7a834f6591a12517618dce1fd69784f6/packages/next/src/client/link.tsx#L180-L191
function isModifiedEvent(event) {
    const eventTarget = event.currentTarget;
    const target = eventTarget.getAttribute('target');
    return target && target !== '_self' || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || // triggers resource download
    event.nativeEvent && event.nativeEvent.which === 2;
}
// copied from https://github.com/vercel/next.js/blob/66f8ffaa7a834f6591a12517618dce1fd69784f6/packages/next/src/client/link.tsx#L204-L217
function shouldPreserveDefault(e) {
    const { nodeName } = e.currentTarget;
    // anchors inside an svg have a lowercase nodeName
    const isAnchorNodeName = nodeName.toUpperCase() === 'A';
    if (isAnchorNodeName && isModifiedEvent(e)) {
        // ignore click for browser’s default behavior
        return true;
    }
    return false;
}
// This is a wrapper around next/link that explicitly uses the router APIs
// to navigate, and trigger a view transition.
function Link(props) {
    const router = useTransitionRouter();
    const { href, as, replace, scroll } = props;
    const onClick = useCallback((e)=>{
        if (props.onClick) {
            props.onClick(e);
        }
        if ('startViewTransition' in document) {
            if (shouldPreserveDefault(e)) {
                return;
            }
            e.preventDefault();
            const navigate = replace ? router.replace : router.push;
            navigate(as || href, {
                scroll: scroll != null ? scroll : true
            });
        }
    }, [
        props.onClick,
        href,
        as,
        replace,
        scroll
    ]);
    return /*#__PURE__*/ jsx(NextLink, _extends({}, props, {
        onClick: onClick
    }));
}

export { Link, ViewTransitions, useTransitionRouter };

And here is how I imported the Link component in the EventCard component:

import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Event } from "@/types/eventsSchema";
import { formatDistanceToNow, isFuture } from "date-fns";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Link } from "next-view-transitions";
import Image from "next/image";

interface EventCardProps {
  event: Event;
  expanded: boolean;
  toggleEventDetails: (id: string) => void;
  handleEnrollClick: (event: Event) => void;
}

const EventCard: React.FC<EventCardProps> = ({
  event,
  expanded,
  toggleEventDetails,
  handleEnrollClick,
}) => {
  const startTime = new Date(event.startTime);
  const endTime = new Date(event.endTime);
  const registrationDeadline = new Date(event.registrationDeadline);

  return (
    <Card className="h-full">
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 h-full p-4">
        {/* Image and Timing Section */}
        <div className="md:col-span-1 relative">
          <Image
            src={event.eventImageUrl || "https://via.placeholder.com/500x300"}
            alt={event.title}
            width={500}
            height={300}
            className="w-full h-full object-cover rounded-lg"
          />

          {/* Start and End Time */}
          <div className="absolute bottom-0 left-0 bg-white p-3 rounded-lg shadow-lg m-4">
            <p className="text-sm">
              <strong>Start:</strong>{" "}
              {formatDistanceToNow(startTime, { addSuffix: true })}
            </p>
            <p className="text-sm">
              <strong>End:</strong>{" "}
              {formatDistanceToNow(endTime, { addSuffix: true })}
            </p>
          </div>
        </div>

        {/* Content Section */}
        <div className="md:col-span-2 p-4 grid grid-rows-[1fr_auto] h-full gap-4">
          <div>
            <h4 className="text-2xl font-semibold mb-3">{event.title}</h4>
            <p className="text-base text-gray-700">{event.summary}</p>
          </div>

          {/* Collapsible Details Section */}
          <Collapsible
            open={expanded}
            onOpenChange={() => toggleEventDetails(event._id)}
          >
            <CollapsibleTrigger asChild>
              <Button variant="outline" className="mt-4">
                {expanded ? "Hide Details" : "View Details"}
              </Button>
            </CollapsibleTrigger>

            <CollapsibleContent>
              <CardContent className="pt-4">
                {/* Description */}
                <div dangerouslySetInnerHTML={{ __html: event.description }} />

                {/* Speakers Section */}
                {event.speakers && event.speakers.length > 0 ? (
                  <div className="mt-4">
                    <h5 className="text-lg font-semibold">Main Speakers:</h5>
                    <ul className="list-disc ml-5 mt-2">
                      {event.speakers.map((speaker, index) => (
                        <li key={index}>
                          <Link
                            href={`/members/${speaker.memberId._id}`}
                            className="text-sm text-secondary ml-2"
                          >
                            {speaker.memberId?.user_id?.first_name ||
                              "Unknown Speaker"}
                          </Link>{" "}
                          - {speaker.speakerRole || "No Role Specified"}
                        </li>
                      ))}
                    </ul>
                  </div>
                ) : (
                  <div className="mt-4">
                    <h5 className="text-lg font-semibold">Main Speakers:</h5>
                    <p>No speakers available for this event.</p>
                  </div>
                )}
              </CardContent>
            </CollapsibleContent>
          </Collapsible>

          {/* Footer Section */}
          <CardFooter className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
            {/* Attendees Count */}
            <div className="text-sm">
              <strong>{event.attendees.length + 50}</strong> already registered
            </div>

            {/* Registration Deadline Badge */}
            {isFuture(registrationDeadline) ? (
              <div className="text-xs">
                Register before:{" "}
                <span className="text-destructive">
                  {formatDistanceToNow(registrationDeadline, {
                    addSuffix: true,
                  })}
                </span>
              </div>
            ) : (
              <div className="text-xs text-muted">Registration closed</div>
            )}

            {/* Enroll Button */}
            {new Date(event.registrationDeadline).getTime() > Date.now() ? (
              <Button onClick={() => handleEnrollClick(event)}>
                Enroll Now
              </Button>
            ) : (
              <Button disabled>Registration Closed</Button>
            )}
          </CardFooter>
        </div>
      </div>
    </Card>
  );
};

export default EventCard;

Please who can tell me what is going wrong.


Solution

  • When you use the app route in this app, all components will be aware of the server component. This is one of the differences between app route and page route at Next.js. So, if you use the client component like this code, you need to add the "use client" code.