higher-order components must provide
- easily extensible layouts
- good default components
- mechanisms to control default components
- easily extensible/swappable components
providing a "free" high-level tree of components whilst offering low-level extensibility can be seen as conflicting objectives.
- providing default components intrinsically defines and locates nodes on the DOM
- needing extensinsibility is specifically the undoing of providing default trees/DOM
providing an API that meets both goals is TBD
other component libraries have various patterns that help composition.
- SUIR has a practice of exporting supporting Components off a root component. e.g.
<Form.Field />
- this is a great short hand that internally does function/component composition
but what about when we want attribute composition without extra DOM?
let's take the case of making a <LoginPage />
screen. i'm working on the <AuthPane />
component,
which must support a variety of auth states:
- sign in
- create account
- forgot password
- reset password
- sign out
- ...maybe others
the <AuthPane />
should host all of the above contents depending on what state it's in.
create new components for each state.
const SignInPane =
<AuthPane>
<Input un />
<Input password />
</AuthPane>
const SignOutPane =
<AuthPane>
<Button onClick={signOut} />
</AuthPane>
drawbacks
- we lose extensiblity of layout
- components are firmly positioned
- we've prescribed an exact component for each control, and each control is not extensible
- we duplicate
<AuthPane />
all over the place, rather than use a common reference in mem
create HOC's that allow overrides, but provide sensible defaults. consider just
the <AuthPane />
with signout support.
<AuthPane signOut />
// by default renders an empty <SignOut /> as a child of the `<AuthPane />`
<AuthPane signOut={<SignOut onSignOut={onSignout}} /> // enables fine customization of the logout
// but ^ is kind of annoying, because now you must import <SignOut /> to customize it
<AuthPane signOut={{ onSignout }} /> // so support POJOs that will get passed as props
// <SignOut {...{ onSignout }} /> so now the user gets _really easy customization_
<AuthPane signOut={<p>or ditch the default component altogether</p>} />
// for ultimate flexibility
<AuthPane>
<span>some content</span>
<AuthPane.SignOut className='auth-pane__signout--my-app-override' />
<span>other content</span>
</AuthPane>
// but this is risky, because in this HOC, <AuthPane.Logout /> value is both
// locating the React.Element into the DOM _and_ providing a component set.
// props.signOut and <AuthPane.SignOut /> cannot be used in tandem