Make your React Components purely presentational, and simply bind the props for them in a declarative manner.
When I tried some different "compose hooks" libraries on npm, for each one I missed some feature or configurability. None of them supported the full control I felt required for all situations, so this version enables just a few key control features.
- 0 dependencies, but of course React is a peer dependecy.
This HOC (Higher Order Component) wraps a component in order to attach hooks, props & behaviors.
It enables the separation of presentation from logic & behavior, like this:
// MyComp.jsx
const MyCompPresenter = ({ ...props }) => (
<View>
<Text>
{`Here is the pure JSX content,`}
{`which means these type of pure component functions Never have bodies, only direct returns of JSX.`}
{`This is because all logic, hooks etc is bound via composeHooks and enter here as props.`}
{`A good pattern is for these presenter components to always end in "...Presenter".`}
</Text>
</View>
);
export const MyComp = composeHooks({
// Hooks and prop creations (See below)
})(MyCompPresenter);
First and foremost:
- Each value will be executed as a function at render.
Then, the composeHooks
basically have 4 behaviors for the key-values in the object given to it:
-
Props from the parent will by default override the props from
composeHooks
. - A prefix
>
in the key string will inject all props up until that point into the value function. - A postfix
!
in the key string will make this prop override that prop possibly coming from the parent. - The result of a value function call will be spread into props if the key string begins with
use
followed by a capital letter, as in e.g.useValue
. (Otherwise the result will directly be assigned to the prop.)
Let's see some examples of what the key-values can be and look like.
export const MyComp = composeHooks({
/* Simplest use. The hook returns an object that will be spread into props. */
useExpanded,
/* Hook returns something/anything that we want to assign to `theme` prop. */
theme: useTheme,
/* We can use a simple function to return a constant value, and... */
noUppercase: () => true,
/* ...with the `!` syntax overwrite the possible incoming prop with the same name. */
'icon!': () => 'arrow-right',
/* Same as above with an object put into `labelStyle` (and overriding parent prop). */
'labelStyle!': () => ({ flex: 1 }),
/* Putting a ">" first will inject all previous props into the function. Hence, we can use `theme` here. */
'>color': ({ theme }) => theme.colors.onPrimary,
/* Combine `>` & `!` to use incomming props and then overwrite.
(This pattern is especially useful for `style`.) */
'>style!': ({ theme, style }) => ({
backgroundColor: theme.colors.onSurfaceVariant,
...style,
}),
/* If our custom hook `useCustomHook` needs some props, but we want the returned object to
be spread into all final props, we can give it a dummy name that follows the naming convention
of React Hooks. */
'>useSpread': useCustomHook,
/* Using e.g. ramda can be a nice point-free style way of selecting/using something from a hook call. */
t: R.pipe(useTranslation, R.head),
})(MyCompPresenter);
A possible output (props for the Presenter) from the above composed hooks etc could be:
const MyCompPresenter = ({
expanded,
toggleExpanded,
theme,
noUppercase,
icon,
labelStyle,
color,
style,
someValueFromCustomHook,
anotherValueFromCustomHook,
t,
}) => (
// ...
);
Happy coding!
MIT