React in patterns / Composition
One of the biggest benefits of React is composability. I personally don't know a framework that offers such an easy way to create and combine components. In this section we will explore few composition techniques which proved to work well.
Let's get a simple example. Let's say that we have an application with a header and we want to place a navigation inside. We have three React components - App
, Header
and Navigation
. They have to be nested into each other so we end up with the following markup:
<App>
<Header>
<Navigation> ... </Navigation>
</Header>
</App>
The trivial approach for combining these components is to reference them in the places where we need them.
// app.jsx
import Header from './Header.jsx';
export default class App extends React.Component {
render() {
return <Header />;
}
}
// Header.jsx
import Navigation from './Navigation.jsx';
export default class Header extends React.Component {
render() {
return <header><Navigation /></header>;
}
}
// Navigation.jsx
export default class Navigation extends React.Component {
render() {
return (<nav> ... </nav>);
}
}
However, following this pattern we introduce several problems:
- We may consider the
App
as a place where we wire stuff, as an entry point. So, it's a good place for such composition. TheHeader
though may have other elements like a logo, search field or a slogan. It will be nice if they are passed somehow from the outside so we don't create a hard-coded dependency. What if we need the sameHeader
component but without theNavigation
. We can't easily achieve that because we have the two bound tightly together. - It's difficult to test. We may have some business logic in the
Header
and in order to test it we have to create an instance of the component. However, because it imports other components we will probably create instances of those components too and it becomes heavy for testing. We may break ourHeader
test by doing something wrong in theNavigation
component which is totally misleading. (Note: while testing the shallow rendering solves this problem by rendering only theHeader
without its nested children.)
In React we have the handy this.props.children
. That's how the parent reads/accesses its children. This API will make our Header agnostic and dependency-free:
// App.jsx
export default class App extends React.Component {
render() {
return (
<Header>
<Navigation />
</Header>
);
}
}
// Header.jsx
export default class Header extends React.Component {
render() {
return <header>{ this.props.children }</header>;
}
};
It's also easy to test because we may render the Header
with an empty <div>
. This will isolate the component and will let us focus on only one piece of our application.
Every React component receive props. It's nice that these props may contain all kind of data. Even other components.
// App.jsx
class App extends React.Component {
render() {
var title = <h1>Hello there!</h1>;
return (
<Header title={ title }>
<Navigation />
</Header>
);
}
};
// Header.jsx
export default class Header extends React.Component {
render() {
return (
<header>
{ this.props.title }
<hr />
{ this.props.children }
</header>
);
}
};
This technique is helpful when we have a mix between components that exist inside the Header
and components that have to be provided from the outside.