Love the work you're doing on this.
Would it makes sense within the context of this library to provide helpers for rendering different components based on props?
For instance conditional() could work like branch() but instead of applying left/right HOC to the base component, it would render base component if test returns true and null if false.
Or switch(), which would get an array of objects: { test: (props: Object) => boolean, component: Class<ReactComponent> } and renders the first one that passes it's test. Base component could act as the default in switch statements.
I use something similar to the above on my current project and would be happy to take a crack at a PR for recompose if this is something that makes sense to you.
Thanks for your suggestions! I'm hesitant to add a conditional() helper because it's not clear from the name how it's different from branch(). However, I've been thinking about adding a renderComponent() helper, which is really just the identity function with a more meaningful name This isn't quite true, clarification below. With this helper, you could achieve the same effect as conditional() like so:
const Component = branch(
test,
renderComponent(Foo), // Foo is rendered if true
renderComponent(Bar) // Bar is rendered if false
)(BaseComponent)
This involves a bit more typing, but it avoids the confusion of whether branch() accepts an HoC or a component. I'm open to also adding your proposed conditional() helper if you can think of a better name for it.
Your switch() idea is also neat, but again, I would want it to operate on HoCs, not components.
EDIT:
renderComponent() would look like this:
const renderComponent = Component => () => Component;
For stateless function components—but not class components—you _could_ replace this with the identity function.
I've added renderComponent() to version 0.5.0. There's an example of how to use it here: https://github.com/acdlite/recompose/blob/master/docs/API.md#rendercomponent
@acdlite I think your resolution was good but there is definitely a place for a switch() that operates on HOC. Is there another issue that attends to this?
I would like to handle 4 cases for a prop:
missing | instanceof Error | empty List | default (non-empty list)
Currently I need to compose 3 x branch HOCs to do this.
Having the same "issue" as @bholloway. I need to render an Avatar based on either:
userId: fetch user from api
user: should contain all info
picture: render simple image
anonymous: show guest icon
<Avatar userId="u1" />
<Avatar user={{ picture: '...' }} />
<Avatar picture="http://....." />
<Avatar />
Base on the info in this thread, I propose we can add a new HOC, cond(), to Recompose, which signature is similar to ramda/cond:
[[(props → Boolean), HOC]] → HOC
Ex:
cond([
[props => props.error, renderComponent(ErrorComponent)],
[props => props.warning, renderComponent(WarningComponent)],
[() => true, a => a]
])
The cond would be awesome.
I wonder how similar this is to a helper I wrote called nonOptimalStates?
@timkindberg They're similar. However, cond is familiar to ramda users :)
And IMO we should let the consumer decide how to compose their HOC, instead of wrapping renderComponent for them.
has cond (https://github.com/acdlite/recompose/issues/21#issuecomment-293528059) been added?
It is not documented: https://github.com/acdlite/recompose/blob/master/docs/API.md
@barbalex No, it hasn't been added.
Even though it has been awhile and in most cases it is recommended to use switch (https://github.com/acdlite/recompose/issues/386#issuecomment-301557841), in case you actually want to use a switch as an HOC -
(The definition is based on https://github.com/acdlite/recompose/issues/21#issuecomment-293528059 and the implementation is mostly inspired by branch implementation)
import { Component, ComponentType, createFactory } from 'react';
import { ComponentEnhancer, InferableComponentEnhancer, setDisplayName, wrapDisplayName } from 'recompose';
type mapper<TInner, TOuter> = (input: TInner) => TOuter;
type predicate<T> = mapper<T, boolean>;
type Condition<TOuter> = [predicate<TOuter>, ComponentEnhancer<any, any> | InferableComponentEnhancer<{}>];
const identity = Component => Component;
export function cond<TOuter = any>(conditions: Array<Condition<TOuter>>): ComponentEnhancer<any, TOuter> {
return (BaseComponent: ComponentType<any>) => {
const factories = Array(conditions.length);
class Cond extends Component<TOuter> {
render() {
const enhancerIndex = conditions.findIndex(([test, hoc]) => test(this.props));
const enhancer = enhancerIndex > -1 ? conditions[enhancerIndex][1] : identity;
factories[enhancerIndex] = factories[enhancerIndex] || createFactory(enhancer(BaseComponent));
return factories[enhancerIndex](this.props);
}
}
if (process.env.NODE_ENV !== 'production') {
return setDisplayName<TOuter>(wrapDisplayName(BaseComponent, 'cond'))(Cond);
}
return Cond;
};
}
@wuct @arturmuller this issue was closed, maybe reopen it, or open a new one with the cond proposal ?
I also think it would be very useful and I'm happy to implement it if needed.
Here's how I've ended up using branch() wrapped around a "null" component:
export default branch(
props => !!canRenderBaseComponent(props),
renderComponent(BaseComponent),
renderNothing
)(() => null);
Had to reinvent this just now, called it branches to avoid import name clash with cond from ramda.
import {
reduceRight,
} from 'ramda';
import {
branch,
renderNothing,
} from 'recompose';
export const branches = reduceRight(([ test, left ], right) => branch(test, left, right), renderNothing);
Usage example:
import {
prop,
T as alwaysTrue,
} from 'ramda';
import {
compose,
renderNothing,
renderComponent,
} from 'recompose';
import { branches } from '../../utils/branches';
const UserDetails = () => 'UserDetails';
const TransactionDetails = () => 'TransactionDetails';
const OverallDetails = () => 'OverallDetails';
export const Details = compose(
/* ... */
branches([
[ prop('selectedUserId'), renderComponent(UserDetails) ],
[ prop('selectedTransactionId'), renderComponent(TransactionDetails) ],
[ alwaysTrue, renderComponent(OverallDetails) ],
])
)(renderNothing);
Most helpful comment
Base on the info in this thread, I propose we can add a new HOC,
cond(), to Recompose, which signature is similar toramda/cond:Ex: