0

Context

When designing the API of a React component, you may decide to receive props in a more semantical way. Here's a simple example

<Modal headerTitle="foo" />

vs

<Modal>
  <Header>Foo</Header>
</Modal

A more complex example would be this one, where we have a table and we need to set responsiveness:

const hideOn = { email: 'smDown'}
const data = [{ name: 'jonh', email: 'john@mail.com' }]
<Table hideOn={hideOn} data={data} />

vs

<Table>
  <Header>
    <HeaderCell>Name</HeaderCell>
    <HeaderCell hideOn="smDown">Email<HeaderCell>
  <Header>
  <Row>
    <Cell>John</Cell>
    <Cell>john@mail.com</Cell>
  </Row>
</Table>

In both scenarios, I'd strongly argue the second design brings more benefits (but not going to discuss that here).

Pattern

Following the example above, in order to get the data for responsiveness of each of the tables Cell, Table need to run a children.find(child => child.type === HeaderCell).props.hideOn and pass the value to the Cell through cloneElement.

Another example would be making a component API strict so that only certain components can be passed. In this example, the hacked div won't be rendered at all:

<Table>
  <Header>
    <div style={{backgroundImage: './foo.png', top: 0, position: 'absolute' }}> 
      Hacked div. Programmer was lazy to figure how to put a background 
      image in the header.
  
      However, this hacked div won't get rendered
    </div>
  <Header>My Modal</Header>
  ...
</Table>

In this example, Table can run a children.find(child => child.type === to find only valid children (Header and Row).

Problem definition

What if we needed to do some decoration or just DRYing up the code, like the following:

<Table>
  <SharedHeaders />
  <Row>
    <Cell>John</Cell>
    <Cell>john@mail.com</Cell>
  </Row>
</Table>

At this point, we see 2 problems:

  1. Table is no longer able to find the hideOn prop, because it doesn't have a Header within its children.
  2. Assuming Header was one of Table valid children, SharedHeader is definitely not one of them.

This leaves me wondering whether the approach itself is fundamentally flawed, and I'm looking at the solution from the incorrect perspective.

I realize there are different options to solve the use case above (such as using the Context API), but I have other use cases where we work by finding a child with specific type, so I want to focus the question specifically on that practice, and its take on composition.

Is it an anti-pattern, when doing composition, for a component to search for a component with a specific type within its children in order to work?

  • There's no such thing as "correct." Antipatterns are labelled as such because they are commonly used, even though their drawbacks exceed their benefits. Is that the case here? – Robert Harvey Jul 28 at 17:40
  • I believe so. If there was a way to have interfaces between react element types, then there would be no drawbacks, but right now there are more than benefits – Christopher Francisco Jul 28 at 21:51
0

It’s not a big issue to use cloneElement on structured, nested data. It’s not surprising if td elements renders differently compared to th. If you want to be explicit, you can always use the render props pattern. As an example, you can pass refs for calling imperative methods from the parent, or pass a callback to indicate that a child has changed. Something like this will suffice:

<Table rowCount={3} {...props}>
  {({onHeaderChange, onRowChangeCallbackList}) => (
    <Header onChange={onHeaderChange} />
    // render rows
  )}
</Table>
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.