1

I’m using shadcn/ui with Radix UI’s Collapsible to make a collapsible filter section.

Wrapper component:

import { ReactNode, useEffect, useState } from "react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";
import { Button } from "./ui/button";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";

export function CollapsibleFilter({
  title,
  children,
  tmp,
}: {
  title: string;
  children: ReactNode;
  tmp?: any;
}) {
  const [open, setOpen] = useState(false);
  useEffect(() => {
    console.log("children", children);
    console.log("tmp", tmp);
  }, [open]);
  return (
    <Collapsible open={open} onOpenChange={setOpen}>
      <CollapsibleTrigger asChild>
        <Button
          variant="ghost"
          size="icon"
          className="w-full justify-between text-base font-normal hover:bg-transparent!"
        >
          {title}
          <ChevronDown
            className={cn(
              open && "rotate-180",
              "transition-transform ease-in-out duration-200"
            )}
          />
        </Button>
      </CollapsibleTrigger>
      <CollapsibleContent className="flex flex-col gap-4 py-3">
        {children}
      </CollapsibleContent>
    </Collapsible>
  );
}

In my page I try to render children from a Map:

import { CollapsibleFilter } from "@/components/collapsible-filter";
import { useEffect, useState } from "react";

export default function Home() {
  const [mapObj, setMap] = useState<Map<string, string>>();
  useEffect(() => {
    setMap(new Map([["John", "abc"]]));
  }, []);
  return (
    <div>
      <h2 className="text-red-800">
        using <code>Array.from(map.entries())</code>
      </h2>
      <CollapsibleFilter title="test1">
        {Array.from(mapObj?.entries() ?? []).map((kv) => {
          return (
            <div className="text-red-400">
              <p>name:{kv[0]}</p>
              <p>tag:{kv[1]}</p>
            </div>
          );
        })}
      </CollapsibleFilter>
      <h2 className="text-blue-800">
        using <code>map.entries()</code>
      </h2>
      <CollapsibleFilter title="test2">
        {mapObj?.entries().map((kv) => {
          return (
            <div className="text-blue-300">
              <p>name:{kv[0]}</p>
              <p>tag:{kv[1]}</p>
            </div>
          );
        })}
      </CollapsibleFilter>

      <h2>outside of collapsible filter</h2>
      {mapObj?.entries().map((kv) => {
        return (
          <div className="text-orange-400">
            <p>name:{kv[0]}</p>
            <p>tag:{kv[1]}</p>
          </div>
        );
      })}
    </div>
  );
}

In test1 (Array.from()) the items render fine. In test2 (mapObj.entries() directly) nothing renders inside the collapsible. But the same {mapObj?.entries().map(...)} expression outside of <CollapsibleFilter> works.

What it looks like:

enter image description here

Why does passing the result returned by mapObj.entries().map as children to CollapsibleFilter render nothing, but passing an array from Array.from(mapObj.entries()).map works? And why does the iterator version still work if I put it outside of the collapsible component?

Full demo repo. Live demo.

First I have a custom component CollapsibleFilter which acts as a wrapper of Collapsible from shadcn/ui. By passing this component with children it will display the content of children when the collapsible is opened. When I write:

<CollapsibleFilter title="foo">
hello world
</CollapsibleFilter>

it should show this in the browser (click the collapsible):

enter image description here

I created a React state variable mapObj:

const [mapObj, setMap] = useState<Map<string, string>>();
useEffect(() => {
setMap(new Map([["John", "abc"]]));
}, []);

Which is an object of type Map and will be initialized with a key value pair of ["John","abc"]. When I write:

<h2>outside of collapsible filter</h2>
{mapObj?.entries().map((kv) => {
        return (
          <div className="text-orange-400">
            <p>name:{kv[0]}</p>
            <p>tag:{kv[1]}</p>
          </div>
        );
})}

As expected, the content of mapObj is displayed:

enter image description here

This goes against this answer which states that React can't render the result returned from an iterator. This ("test1"):

<h2 className="text-red-800">
        using <code>Array.from(map.entries())</code>
</h2>
<CollapsibleFilter title="test1">
        {Array.from(mapObj?.entries() ?? []).map((kv) => {
          return (
            <div className="text-red-400">
              <p>name:{kv[0]}</p>
              <p>tag:{kv[1]}</p>
            </div>
          );
        })}
</CollapsibleFilter>

also works as expected. The content of mapObj is inside my CollapsibleFilter component, click to open the collapsible:

enter image description here

However if I write ("test2"):

<h2 className="text-blue-800">
  using <code>map.entries()</code>
</h2>
<CollapsibleFilter title="test2">
  {mapObj?.entries().map((kv) => {
    return (
      <div className="text-blue-300">
        <p>name:{kv[0]}</p>
        <p>tag:{kv[1]}</p>
      </div>
    );
  })}
</CollapsibleFilter>

against expectation, when I click the collapsible there is nothing:

enter image description here

Why does "test1" work but "test2" fail?

4
  • What do you mean with "...outside of <CollapsibleFilter> and render it directly in the page, it works."? The image you have included is unreadable: what is it supposed to show? Note that the "reproduction" should not be behind a link. Can you edit your question to show what code you have used to do it "outside of"? Commented Aug 8 at 14:13
  • @trincot sorry for my expression, by "...outside of <CollapsibleFilter> and render it directly in the page, it works." i was trying to say when passing the object returned by {mapObj?.entries().map(...)} as children to <CollapsibleFilter> component it couldn't render the content, but if i put the exact same {mapObj?.entries().map(...)} outside of <CollapsibleFilter> it renders ok. The image is what the Home component looks in the browser, the name:John tag:abc should have shown up between "test2" and "outside of collapsible filter" but it didn't Commented Aug 8 at 15:16
  • I still don't understand what you mean with "outside of". Can you present the code where that works? Commented Aug 8 at 17:18
  • @trincot OP is rendering the iterator as one of the children of the top-level <div> in Home, after the last <h2> element, on the same level as the <CollapsibleFilter> element. That's what they mean by "outside" - it's not passed as children to CollapsibleFilter itself. It's pretty clear from even being titled by <h2>outside of collapsible filter</h2> imo. Commented Aug 9 at 0:45

1 Answer 1

-1

Map.entries() returns an iterator and not an array. Therefore, React is unable to render it. You can use Array.from() or [...Map.entries()] (spread operator) to create an array from the iterator and use it.

Sign up to request clarification or add additional context in comments.

1 Comment

If react is unable to render it, why when i put {mapObj?.entries().map(...)} expression outside of CollapsibleFilter it renders succesfully?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.