Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A component mounted at two points

I'm new to React and am trying to implement an HTML structure like this

<div class="heads">
    <div class="head">head1</div>
    <div class="head">head2</div>
    <div class="head">head3</div>
</div>
<div class="bodies">
    <div class="body">body1</div>
    <div class="body">body2</div>
    <div class="body">body3</div>
</div>

Each head-body pair (like head1-body1) belongs to a single component. Without changing the structure, how would I write such a component?

From my understanding, it needs to be "mounted" at two different points (in "heads" and in "bodies").

A practical example of such structure would be a tabbed dialog which has tab headers within the "heads" div and their contents within "bodies".

What I'm looking for is some code that would be able to translate this:

<Tabs ...>
    <Tab head="head1">body1</Tab>
    <Tab head="head2">body2</Tab>
    <Tab head="head3">body3</Tab>
</Tabs>        

into the above HTML structure. Is that possible in React?

like image 857
gog Avatar asked Feb 11 '26 12:02

gog


2 Answers

This isn't a natural fit in React, but you do have some options.

Option 1: You could bite the bullet and render the head and body components separately, just like you'd write the HTML separately. For example, Material-UI, one of the popular React component libraries, does this for its Tabs component.

Option 2: Components are just code, so you can manipulate them however you want. (In the following example, I made up some unique IDs for each tab so that I have a key prop to keep React happy, and I'm also demonstrating dynamically adding/removing elements.)

function MyPage({hideTab3}) {
  const tabs = [
    { id: 1, header: 'head1', body: <MyBody1 /> },
    { id: 2, header: 'head2', body: <MyBody2 /> },
    !hideTab3 && { id: 3, header: 'head3', body: <MyBody3 /> },
  ].filter(Boolean);

  return (
    <div className="heads">
      {tabs.map(({id, head}) => <div className="head" key={id}>{head}</div>)}
    </div>
    <div className="bodies">
      {tabs.map(({id, body}) => <div className="body" key={id}>{body}</div>)}
    </div>
  )
}

Options 3: You could use component slots, which can be simple or quite sophisticated.

Here's an example of the more sophisticated approach - a component can examine its child components' props and handle their slots themselves. I'm using TypeScript here to help enforce the restrictions on child components.

interface TabProps {
  head: ReactNode;
  children: ReactNode;
}

function Tab({ head, children }: TabProps) {
  return <div className="body">{children}</div>;
}

interface TabsProps {
  // `children` is normally a generic `ReactNode`; specify
  // a strong type here, since we manipulate child props.
  children:
    | ReactElement<TabProps, typeof Tab>
    | ReactElement<TabProps, typeof Tab>[];
}

function Tabs({ children }: TabsProps) {
  return (
    <>
      <ul className="heads">
        {Children.map(children, (tab, i) => (
          // Tab head loop: Extract out child props, as shown in
          // https://sandroroth.com/blog/react-slots#slots-by-type.
          //
          // Production-ready should maybe throw for non-Tab children.
          <li className="head" key={i}>
            {tab.props.head}
          </li>
        ))}
      </ul>

      {/* Bodies can show children's default renders. */}
      <div className="bodies">{children}</div>
    </>
  );
}

export default function App() {
  return (
    <Tabs>
      <Tab head="head1">body 1</Tab>
      <Tab head="head2">body 2</Tab>
    </Tabs>
  );
}

Personally, I'm less likely to use this approach - as mentioned in the sandroroth.com article, it can interfere with composability (having one component inspect another's props no longer works well if you start wrapping and customizing components), and I tend to favor "explicit and composable" over "magical and elegant." But it's a perfectly valid technique.

Other options: You could try more advanced techniques, like using a portal to let a body render its heading elsewhere in the DOM - React's own portal is designed for rendering into non-React nodes, but third-party components like Material-UI's Portal may provide some ideas. Or provide a React context that lets each body register itself via useEffect to specify what tabs should be rendered. These options are more complicated and likely harder to maintain - I wouldn't go down this path unless the other options really wouldn't work for whatever reason.

like image 72
Josh Kelley Avatar answered Feb 13 '26 16:02

Josh Kelley


Yes, you can do it easily.

Please refer below structure.

const App = () => {
    const [tab, setTab] = useState("tab1");

    return (
        <div>
            <div class="heads">
                <div onClick={()=>{setTab("tab1")}} class="head">head1</div>,
                <div onClick={()=>{setTab("tab2")}} class="head">head2</div>,
                <div onClick={()=>{setTab("tab3")}} class="head">head3</div>
            </div>
            <div class="bodies">
                {{
                    "tab1": <div class="body">body1</div>,
                    "tab2": <div class="body">body2</div>,
                    "tab3": <div class="body">body3</div>
                }[tab]}
            </div>
        </div>
    )
}

If you want dynamic tabs you can integrate like this

const App = () => {
    const [tabs, setTabs] = useState([
       {head: "Head 1", body: <div>body1</div>},
       {head: "Head 2", body: <div>body2</div>},
       {head: "Head 3", body: <div>body3</div>},
       {head: "Head 4", body: <div>body4</div>},
       {head: "Head 5", body: <div>body5</div>},       
       {head: "Head n", body: <div>bodyn</div>},
    ]);
    const [tab, setTab] = useState(0);

    return (
        <div>
            <div class="heads">
                {
                    tabs.map((tab, index) => (<div onClick={()=>{setTab(index)}}>{tab.head}</div>))
                }
            </div>
            <div class="bodies">
                {tabs?.[index]?.body}
            </div>
        </div>
    )
}

And then you will get the same structure with

<Tabs ...>
    <Tab head="head1">body1</Tab>
    <Tab head="head2">body2</Tab>
    <Tab head="head3">body3</Tab>
</Tabs>    

Just you will need to add CSS

// For example
.heads {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  border: 1px solid #e0e0e0;
}

.bodies {
   padding: 4px;
   border: 1px solid #f0f0f0;
}
....
like image 28
Nico Peck Avatar answered Feb 13 '26 17:02

Nico Peck



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!