TransWikia.com

Can I have Component interfaces in React?

Stack Overflow Asked on December 22, 2021

Say I have a <Modal> that takes a <Header> <Content> and <Footer>.

(
  <Modal>
    <Header>Foo</Header>
    <Content>Foo</Content>
    <Footer>Foo</Footer>
  </Modal>
)

Now, inside my Modal component I’ll probably have code like the following:

const header = children.find(child => child.type === Header)

In order to get a reference to the rendered header.

Now, what if from the consumer of the modal, I needed a decorated Header. Let’s just call it DecoratedHeader

// DecoratedHeader
const DecoratedHeader = () => <Header>Foo <Icon type="lock" /></Header>

// consumer
(
  <Modal>
    <DecoratedHeader />
    <Content>Foo</Content>
    <Footer>Foo</Footer>
  </Modal>
)

The line above wouldn’t work anymore, as DecoratedHeader type is not Header. However, it IS rendering a Header.

It feels like there’s the concept of "interface" which is missing. Ultimately, the Modal cares for a Header to be rendered, but if you wrap it under a "custom" component there’s no way for it to know that it is still a Header.

What am I missing?

EDIT

To expand more about my use cases, I don’t need an alternative solution. I need to know whether React has support for a mechanism equivalent to an interface, where 2 different Components that comply with the Liskov Substitution Principle (meaning they’re swappable) can have a way to be picked by the parent.

Specifically, replacing this "hardcoded implementation" search, with an "interface" search:

-const specificChild = children.find(child => child.type === SomeComponent)
+const componentInterface = children.find(child => ????)

 // Get a prop out of that component interface
 const { someInterfaceProp } = componentInterface.props;

 return (
   <div>
     {componentInterface}  {/* render it on a specific place */}
   </div>
 )

One Answer

Assuming the only thing you're going to be doing with these components is rendering them in specific spots of the modal, i would do them as separate props. For example:

const Modal = ({ header, content, footer }) => {
  return (
    <div>
      {header}
      <SomethingElseAllModalsHave />
      {content}
      {footer}
    </div>
  )
}

// ... used like:
const Example = () => {
  return (
    <Modal
      header={<DecoratedHeader />}
      content={<Content>Foo</Content>}
      footer={<Footer>Foo</Footer>}
    />
  )
}

If you need the modal to not just render the other components, but give them some information too, you could use a render prop. Basically the same as my example above, but now you pass in functions instead of elements

const Modal = ({ header, content, footer }) => {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div>
      {header(isVisible)}
      <SomethingElseAllModalsHave />
      {content(isVisible)}
      {footer(isVisible}
    </div>
  )
}

// ... used like:
const Example = () => {
  return (
    <Modal
      header={() => <DecoratedHeader />}
      content={(isVisible) => <Content>{isVisible ? "Foo" : "Bar"</Content>}
      footer={(isVisible) => isVisible ? <Footer>Foo</Footer> : null}
    />
  )
}

EDIT:

When you write the JSX <DecoratedHeader/>, the object that is produced contains no information about <Header>. It's basically just an object with a type (ie, a reference to DecoratedHeader) and some props (none in this case). Header only enters the picture when DecoratedHeader is rendered, which won't be until after Modal is rendered.

So whatever the characteristics are that Modal will use to identify what is and is not a header, it needs to be something that is on DecoratedHeader, not just on Header. Perhaps you could add a static property to any component that counts as a header, and then check for that:

const Header = () => {
  // Whatever the code is for this component.
}

Header.isHeader = true;

const DecoratedHeader = () => <Header>Foo <Icon type="lock" /></Header>

DecoratedHeader.isHeader = true;

Then you'll look for it something like this (you should use React.Children, because children is not guaranteed to be an array):

const header = React.Children.toArray(children).find(child => child.type.isHeader);

Answered by Nicholas Tower on December 22, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP