fuma-nama / fumadocs

The beautiful docs framework with Next.js. Alternative to Nextra
https://fumadocs.vercel.app
MIT License
1.86k stars 113 forks source link

fix: search component in fumadocs-ui package #956

Closed jayden-unho closed 4 weeks ago

jayden-unho commented 1 month ago

Problem

onOpenChange inside search dialog component is not working in some situlations

I needed to customize the use of the button that opens the Search Dialog to place it where I wanted it to be

While looking for a way to do this, I found your docs (link)

export default function CustomSearch() {
  const [isOpen, setIsOpen] = useState(false)
  // ...

  return (
    <>
       // ...
       <SearchDialog
        open={isOpen}
        onOpenChange={setIsOpen}
        // ...
      />
    </>
  )
}

However, clicking the ESC button or clicking a result in the Dialog list did not close the Search Dialog

https://github.com/user-attachments/assets/b3820143-2bfa-4461-8853-48b56c3e7ae7

Solution & Suggest

I solved the problem in the same way as this PR

I propose to apply this fix

Changes

changeset-bot[bot] commented 1 month ago

⚠️ No Changeset found

Latest commit: 2dc6512a2b64dd2c25fb2c277b199e96e4520df3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

vercel[bot] commented 1 month ago

@jayden-unho is attempting to deploy a commit to the Fuma Team on Vercel.

A member of the Team first needs to authorize it.

fuma-nama commented 1 month ago

The dialog actually relies on the useSearchContext() context provided by your Root Provider, this is required for shortcuts and lazy loading.

You can import the context hook from fumadocs-ui/provider, and use it instead of a custom useState which is not compatible with Root Provider and its search.hotKey API.

ShivanshVij commented 4 weeks ago

The other option is to just call the props.onOpenChange function in your CustomSearch component with a useEffect hook.

The following component, for example, works really well:

'use client';

import { useDocsSearch } from 'fumadocs-core/search/client';
import {type ReactNode, type ReactElement, useEffect, useState, useContext} from 'react';
import { useOnChange } from 'fumadocs-core/utils/use-on-change';
import {SearchDialog, type SharedProps, type TagItem, TagsList,} from 'fumadocs-ui/components/dialog/search';

import {SearchContext} from "@/context/search";

export interface CustomSearchDialogProps extends SharedProps {
    defaultTag?: string;
    tags?: TagItem[];
    api?: string;
    delayMs?: number;
    footer?: ReactNode;
}

export default function CustomSearchDialog({defaultTag, tags, api, delayMs, ...props}: CustomSearchDialogProps): ReactElement {
    const [tag, setTag] = useState(defaultTag);
    const { search, setSearch, query } = useDocsSearch('', tag, api, delayMs);

    // @ts-ignore
    const {searchOpen, setSearchOpen} = useContext(SearchContext);

    useEffect(() => {
        if(props.open !== searchOpen) {
            setSearchOpen(props.open);
        }
    }, [props.open]);

    useEffect(() => {
        if(props.open !== searchOpen) {
            props.onOpenChange(searchOpen);
        }
    }, [searchOpen]);

    useOnChange(defaultTag, (v) => {
        setTag(v);
    });

    return (
        <SearchDialog
            search={search}
            onSearchChange={setSearch}
            isLoading={query.isLoading}
            results={query.data ?? []}
            {...props}
            open={searchOpen}
            footer={
                tags ? (
                    <>
                        <TagsList tag={tag} onTagChange={setTag} items={tags} />
                        {props.footer}
                    </>
                ) : (
                    props.footer
                )
            }
        />
    );
}

The key is that fumadocs will pass in the onOpenChange function as part of the props. If you don't call that function when your button opens the search modal, then the key capture won't be registered. If you do call the function, everything works as expected.

I'm using contexts above to open and close the modal but useState will work just as well.

fuma-nama commented 4 weeks ago

Thanks for the explanation of @ShivanshVij, notice that you can use the useOnChange hook to avoid useEffect.

closing this for now, I think it would be better to expose the props from root provider