Justin Grant
Justin Grant

Reputation: 46723

Pattern for react components that require specific contained components

What's the idiomatic React way to write a component that nests specific child components? I know how to write a component that simply wraps props.children, like this example from React's own docs:

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

But what if I wanted to create a pair of components that can be used like this:

<TabSet>
  <Tab name="Mammals">
     content for <b>warm blooded</b> creatures here
  </Tab>
  <Tab name="Fish">
     content for <b>cold blooded</b> creatures here
  </Tab>
</TabSet>

Here's my initial implementation of TabSet (using reactstrap), simplified to remove styling, selected-tab management, and other stuff not related to this question.

import React, {Fragment, Component} from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';

export default class TabSet extends Component {

  render(props) { 
    return (
      <Fragment>
        <Nav tabs>
          {props.children.map((tab,i) => 
            <NavItem key={i}>
              <NavLink>
                { tab.name }
              </NavLink>
            </NavItem>
          )}
        </Nav>
        <TabContent>
          {props.children.map((tab,i) => 
            <TabPane key={i} tabId={i}>
              { tab.children }
            </TabPane>
          )}
        </TabContent>
      </Fragment>
    );
  }

}

Where I'm stuck is how to implement the Tab component. Logically, it's part of the TabSet component-- it should never stand alone. And its implementation should be painfully simple because it doesn't actually do anything-- it's just a container for a name attribute and child elements.

So, here's a few questions:

Apologies for what's probably an obvious question-- I'm a newbie to both React and ES6.

Upvotes: 2

Views: 159

Answers (2)

Moti Korets
Moti Korets

Reputation: 3748

If the component is only used in context of another component it is logical to put them both in same module and many libraries do that. The way to achieve this is use multiple export statements without default. You are allowed to use one export default and as many export statements as you need. Like this

export default class TabSet 
...
export class Tab

and to import

import TabSet, {Tab} from './Tab'

The general syntax being

import defaultExport, { namedExport1, namedExport2  } from "module"

The syntax might seem a bit confusing here is the reference

Upvotes: 1

Matt Way
Matt Way

Reputation: 33179

Where you are using export default class ... you can actually export your own object here. With that in mind, you are able to something like:

const TabSet = props => (
  <Your Tabset markup here>
)

const Tab = props => (
  <Your Tab markup here>
)

export {
  Tabset,
  Tab
}

Doing it like this will allow you to import both components in the one line by doing:

import { Tabset, Tab } from 'wherever'

Now while this is one way to do it, and although you think Tab is quite simple, I still believe they belong in their own files. So just create the two class files for Tabset and Tab, but then make a third file called tabs.js or something. It should contain the link to both, like:

import Tabset from './tabset'
import Tab from './tab'

export {
  Tabset,
  Tab
}

This way you have designated files for each component, and you can import them as a single import.


Also for bonus, if you use the PropTypes ability of react, you can restrict the children of the Tabset to actually be your Tabs. Here is the overview, but as an example you can do something like:

// untested
static propTypes = {
  children: PropTypes.arrayOf(PropTypes.instanceOf(Tab))
}

You will have to import the Tab component into the set component to do this.

Upvotes: 0

Related Questions