React patterns
- 10 React mini-patterns, worth reading:
- #4 Controlling CSS with props
- #5 The switching component
- #7 Almost-components: Don’t prematurely componentize. Components aren’t like teaspoons; you can have too many.
- #9 The store is the component’s servant: For this last point, I recommend a single module that does all the massaging of incoming data (oh la la). Renaming props, casting strings to numbers, objects into arrays, date strings to date objects, whatever. Do it all in the one place, and unit test the crap out of it.
- The Common Patterns of React
- Higher Order Components
- Function as Child Component
- React Context for data propagation
- Notification callbacks
- Delegation for passing multiple callbacks
- Container and Presentation Components
- Compound Components
Container Component approach
- Article about Container Components on Medium
- (In Polish) Podział odpowiedzialności komponentów ReactJS - dobry przykład container component
The container of a component is responsible for fetching data. Below you can see an example of a container component, that is passing its state to its child component:
import React from 'react'
import CommentList from './component/CommentList'
class CommentListContainer extends React.Component {
state = {
comments: []
}
componentDidMount () {
fetchSomeComments(comments =>
this.setState({ comments: comments })
)
}
render () {
return (
<CommentList comments={this.state.comments} />
)
}
}
Below is the component, which data is passed to by the container - in this example comments were passed:
import React from 'react'
const CommentList = ({
comments = []
}) => {
return (
<ul>
{
comments.map(comment => (
<li>{comment.body}—{comment.author}</li>
))
}
</ul>
)
}
Below is an example without using Container Component approach:
class CommentList extends React.Component {
state = {
comments: []
}
componentDidMount () {
fetchSomeComments(comments =>
this.setState({ comments: comments })
)
}
render () {
return (
<ul>
{
this.state.comments.map(comment => (
<li>{comment.body}—{comment.author}</li>
))
}
</ul>
)
}
}
Stateless component
Stateless component doesn't have internal state or lifecycle methods. It is using functional approach and an example component could look like below:
const CommentList = ({
comments = []
}) => {
return (
<ul>
{
comments.map(comment => (
<li>{comment.body}—{comment.author}</li>
))
}
</ul>
)
}
Below one is an equivalent:
const CommentList = props => {
return (
<ul>
{
props.comments.map(comment => (
<li>{comment.body}—{comment.author}</li>
))
}
</ul>
)
}
CommentList.defaultProps = {
comments: []
}
Anyway, don’t convert all classes to functional components because some lint rule told you it’s good. Don’t separate "presentational components" until you actually use them twice. Etc. Separating everything out prematurely also makes changes harder, just in a different way.
Reusing parent components like lists for an another type of a child component by using React.createElement
method
In below example, we will use the List
component to render lists of two types of components: Profile
and Post
component.
Profile.js
import React from 'react'
export default class Profile extends React.Component {
renderDetails (key, label) {
if (this.props[key]) {
return (
<div className="detail">{ label } { this.props[key] }</div>
)
}
}
render () {
return (
<li>
<img href={ this.props.imagePath } align="left" width="30" height="30" />
<div className="profile-description">
{ this.props.description }
</div>
{ this.renderDetails('email', 'Email:') }
{ this.renderDetails('twitter', 'Twitter:') }
{ this.renderDetails('phone', 'Phone:') }
</li>
)
}
}
List.js
import React from 'react'
import Profile from './Profile'
class List extends React.Component {
render () {
return (
<ul>
{
this.props.profile.map((profile, index) => {
let newProps = Object.assign({ key: index }, profile)
return React.createElement(this.props.itemRenderer, newProps)
})
}
</ul>
)
}
}
List.propTypes = { itemRenderer: React.PropTypes.func }
List.defaultProps = { profile: [], itemRenderer: Profile }
export default List
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import List from './components/List'
import Profile from './components/Profile'
import Post from './components/Post'
let profileData = [ ... ] // psuedo code, this has all our profile data
let postsData = [ ... ] // psuedo code, this has all our post data
class App extends React.Component {
render () {
return (
<div>
<List items={ profileData } />
<List items={ postsData } itemRenderer={ Post } />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('mount-point'))
HOC - Higher Order Component
HOC extends the state/behavior of the inner component in a composable way. You can use many HOCs on the inner component. HOCs are similar to Higher Order Functions. There are several ways to create HOC what you'll find described in this article.
formGroup.js
import React from 'react'
import { isString } from 'lodash'
function formGroup (Component, config) {
const FormGroup = React.createClass({
__renderLabel () {
// check if the passed value is a string using Lodash#isString
if (isString(this.props.label)) {
return (
<label className="form-label" htmlFor={ this.props.name }>
{ this.props.label }
</label>
)
}
},
__renderElement () {
// We need to see if we passed a Component or an Element
// such as Profile vs. <input type="text" />
if (React.isValidElement(Component)) {
return React.cloneElement(Component, this.props)
}
return (
<Component { ...this.props } />
)
},
render () {
return (
<div className="form-group">
{ this.__renderLabel() }
{ this.__renderElement() }
</div>
)
}
})
return (
<FormGroup { ...config } />
)
}
export default formGroup
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import formGroup from './higherOrderComponents/formGroup'
let MyComponent = React.createClass({
render () {
return (
<div>
{
formGroup(
<input type="text" />,
{ label: 'First Name:', name: 'firstName' }
)
}
</div>
)
}
})
ReactDOM.render(<MyComponent />, document.getElementById('mount-point'))
Render Props - Function as Child Component - as an alternative to HOCs
You have to create a component with a render property (i.e. <Mouse render={value => <span>{value}</span>
). The component can pass values (value
) to a function defined in render prop (i.e. in render you use {this.props.render(this.state)}
to pass this.state
to the render
function property). Then you can access the passed values in the render function (<Mouse render={value => <span>{value}</span>
).
The render props were used in library react-motion which helps you create smooth animations in React.
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
Switching component (components mapper)
Discussed in 10 React mini-patterns, #5 The switching component.
import HomePage from './HomePage.jsx'
import AboutPage from './AboutPage.jsx'
import UserPage from './UserPage.jsx'
import FourOhFourPage from './FourOhFourPage.jsx'
const PAGES = {
home: HomePage,
about: AboutPage,
user: UserPage
}
const Page = (props) => {
const Handler = PAGES[props.page] || FourOhFourPage
return <Handler {...props} />
}
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired
}
Don’t prematurely componentize
Discussed in 10 React mini-patterns, #7 Almost-components.
Don’t prematurely componentize. Components aren’t like teaspoons; you can have too many.
What I am not saying: “take something that you think should be a component, and make it part of the parent component.”
What I am saying: “take something that you don’t think should be a component, and make it a bit more like its own component (if it can be).”
I think
const SearchSuggestions = (props) => {
// renderSearchSuggestion() behaves as a pseduo SearchSuggestion component
// keep it self contained and it should be easy to extract later if needed
const renderSearchSuggestion = listItem => (
<li key={listItem.id}>{listItem.name} {listItem.id}</li>
)
return (
<ul>
{props.listItems.map(renderSearchSuggestion)}
</ul>
)
}
Don't process data in component but in store
Discussed in 10 React mini-patterns, #9 The store is the component’s servant.
For this last point, I recommend a single module that does all the massaging of incoming data (oh la la). Renaming props, casting strings to numbers, objects into arrays, date strings to date objects, whatever.
Do it all in the one place, and unit test the crap out of it.
fetch(`/api/search?${queryParams}`)
.then(response => response.json())
.then(normalizeSearchResultsApiData) // the do-it-all data massager
.then(normalData => {
// dispatch normalData to the store here
})