-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Recipes
It's good to understand how to write your own HOC without recompose so you understand how Recompose can help reduce boilerplate. This example does not use Recompose to create two HOCs. See it in Plunkr
const { Component } = React;
const overrideProps = (overrideProps) => (BaseComponent) => (props) =>
<BaseComponent {...props} {...overrideProps} />;
const alwaysBob = overrideProps({ name: 'Bob' });
const neverRender = (BaseComponent) =>
class extends Component {
shouldComponentUpdate() {
return false;
}
render() {
return <BaseComponent {...this.props} />;
}
};
const User = ({ name }) =>
<div className="User">{ name }</div>;
const User2 = alwaysBob(User);
const User3 = neverRender(User);
const App = () =>
<div>
<User name="Tim" />
<User2 name="Joe" />
<User3 name="Steve" />
</div>;
by @kindberg
Use compose
to mix and match HOC from recompose and other libraries such as redux' connect
HOC.
See it in Plunkr
const { Component } = React;
const { compose, setDisplayName, setPropTypes } = Recompose;
const { connect } = Redux();
const enhance = compose(
setDisplayName('User'),
setPropTypes({
name: React.PropTypes.string.isRequired,
status: React.PropTypes.string
}),
connect()
);
const User = enhance(({ name, status, dispatch }) =>
<div className="User" onClick={
() => dispatch({ type: "USER_SELECTED" })
}>
{ name }: { status }
</div>
);
by @kindberg
Can help with interactions that require a simple show/hide such as tooltips, dropdowns, expandable menus, etc. See it in Plunkr
const { Component } = React;
const { compose, withState, withHandlers } = Recompose;
const withToggle = compose(
withState('toggledOn', 'toggle', false),
withHandlers({
show: ({ toggle }) => (e) => toggle(true),
hide: ({ toggle }) => (e) => toggle(false),
toggle: ({ toggle }) => (e) => toggle((current) => !current)
})
)
const StatusList = () =>
<div className="StatusList">
<div>pending</div>
<div>inactive</div>
<div>active</div>
</div>;
const Status = withToggle(({ status, toggledOn, toggle }) =>
<span onClick={ toggle }>
{ status }
{ toggledOn && <StatusList /> }
</span>
);
const Tooltip = withToggle(({ text, children, toggledOn, show, hide }) =>
<span>
{ toggledOn && <div className="Tooltip">{ text }</div> }
<span onMouseEnter={ show } onMouseLeave={ hide }>{ children }</span>
</span>
);
const User = ({ name, status }) =>
<div className="User">
<Tooltip text="Cool Dude!">{ name }</Tooltip>—
<Status status={ status } />
</div>;
const App = () =>
<div>
<User name="Tim" status="active" />
</div>;
by @kindberg
And again but using withReducer
helper See it in Plunkr
const { Component } = React;
const { compose, withReducer, withHandlers } = Recompose;
const withToggle = compose(
withReducer('toggledOn', 'dispatch', (state, action) => {
switch(action.type) {
case 'SHOW':
return true;
case 'HIDE':
return false;
case 'TOGGLE':
return !state;
default:
return state;
}
}, false),
withHandlers({
show: ({ dispatch }) => (e) => dispatch({ type: 'SHOW' }),
hide: ({ dispatch }) => (e) => dispatch({ type: 'HIDE' }),
toggle: ({ dispatch }) => (e) => dispatch({ type: 'TOGGLE' })
})
);
// Everything else is the same...
by @kindberg
In this example we take a flexible and generic UserList component and we populate it with specific users three ways to form three specific components that will filter out only the users that the component is meant to show. See it in Plunkr
const { Component } = React;
const { mapProps } = Recompose;
const User = ({ name, status }) =>
<div className="User">{ name }—{ status }</div>;
const UserList = ({ users, status }) =>
<div className="UserList">
<h3>{ status } users</h3>
{ users && users.map((user) => <User {...user} />) }
</div>;
const users = [
{ name: "Tim", status: 'active' },
{ name: "Bob", status: 'active' },
{ name: "Joe", status: 'active' },
{ name: "Jim", status: 'inactive' },
];
const filterByStatus = (status) => mapProps(
({ users }) => ({
status,
users: users.filter(u => u.status === status)
})
);
const ActiveUsers = filterByStatus('active')(UserList);
const InactiveUsers = filterByStatus('inactive')(UserList);
const PendingUsers = filterByStatus('pending')(UserList);
const App = () =>
<div className="App">
<ActiveUsers users={ users } />
<InactiveUsers users={ users } />
<PendingUsers users={ users } />
</div>;
by @kindberg
It can be helpful to save a certain configuration of props into a pre-configured component. See it in Plunkr
const { Component } = React;
const { withProps } = Recompose;
const HomeLink = withProps(({ query }) => ({ href: '#/?query=' + query }))('a');
const ProductsLink = withProps({ href: '#/products' })('a');
const CheckoutLink = withProps({ href: '#/checkout' })('a');
const App = () =>
<div className="App">
<header>
<HomeLink query="logo">Logo</HomeLink>
</header>
<nav>
<HomeLink>Home</HomeLink>
<ProductsLink>Products</ProductsLink>
<CheckoutLink>Checkout</CheckoutLink>
</nav>
</div>;
by @kindberg
Pretty self explanatory :) See it in Plunkr
const { Component } = React;
const { compose, lifecycle, branch, renderComponent } = Recompose;
const withUserData = lifecycle({
state: { loading: true },
componentDidMount() {
fetchData().then((data) =>
this.setState({ loading: false, ...data }));
}
});
const Spinner = () =>
<div className="Spinner">
<div className="loader">Loading...</div>
</div>;
const isLoading = ({ loading }) => loading;
const withSpinnerWhileLoading = branch(
isLoading,
renderComponent(Spinner)
);
const enhance = compose(
withUserData,
withSpinnerWhileLoading
);
const User = enhance(({ name, status }) =>
<div className="User">{ name }—{ status }</div>
);
const App = () =>
<div>
<User />
</div>;
by @kindberg
The idea here is that your components could contain only the "happy path" rendering logic. All other unhappy states—loading, insufficient data, no results, errors—could be handled via a nonOptimalStates
HOC. See it in Plunkr
const { Component } = React;
const { compose, lifecycle, branch, renderComponent } = Recompose;
const User = ({ name, status }) =>
<div className="User">{ name }—{ status }</div>;
const withUserData = lifecycle({
componentDidMount() {
fetchData().then(
(users) => this.setState({ users }),
(error) => this.setState({ error })
);
}
});
const UNAUTHENTICATED = 401;
const UNAUTHORIZED = 403;
const errorMsgs = {
[UNAUTHENTICATED]: 'Not Authenticated!',
[UNAUTHORIZED]: 'Not Authorized!',
};
const AuthError = ({ error }) =>
error.statusCode &&
<div className="Error">{ errorMsgs[error.statusCode] }</div>;
const NoUsersMessage = () =>
<div>There are no users to display</div>;
const hasErrorCode = ({ error }) => error && error.statusCode;
const hasNoUsers = ({ users }) => users && users.length === 0;
const nonOptimalStates = (states) =>
compose(...states.map(state =>
branch(state.when, renderComponent(state.render))));
const enhance = compose(
withUserData,
nonOptimalStates([
{ when: hasErrorCode, render: AuthError },
{ when: hasNoUsers, render: NoUsersMessage }
])
);
const UserList = enhance(({ users, error }) =>
<div className="UserList">
{ users && users.map((user) => <User {...user} />) }
</div>
);
const App = () =>
<div className="App">
<UserList />
</div>;
by @kindberg
For more great Recompose learning watch @timkindberg's egghead.io course.