Hi Habr! I offer you a free translation of the article “
React Patterns ” by Michael Chan, with some of my notes and additions.
First of all I would like to thank the author of the original text. Translated, I used the term “Simple Component” as a designation for Stateless Component aka Dump Component aka Component vs Container
Constructive criticism, as well as alternative patterns and features React welcome in the comments.
Table of contents- Simple components - Stateless function
- JSX Attribute Distribution - JSX Spread Attributes
- Destructuring Arguments - Destructuring Arguments
- Conditional Rendering
- Types of descendants - Children Types
- Array as a descendant - Array as children
- Function as a child - Function as children
- Render function - Render callback
- Descendants Pass - Children pass-through
- Component Redirection - Proxy component
- Styling components - Style component
- Event switch - Event switch
- Component Layout - Layout component
- Component container - Container component
- Higher-order components
Go!
Stateless function
A stateless function (hereinafter referred to as Simple Copons) is a great way to define a universal component. They do not contain a state (state) or a reference to a DOM element (ref), these are just functions.
')
const Greeting = () => <div>Hi there!</div>
They pass parameters (props) and context
const Greeting = (props, context) => <div style={{color: context.color}}>Hi {props.name}!</div>
They can define local variables if they use blocks ({})
const Greeting = (props, context) => { const style = { fontWeight: "bold", color: context.color, } return <div style={style}>{props.name}</div> }
But you can get the same result if you use another function.
const getStyle = context => ({ fontWeight: "bold", color: context.color, }) const Greeting = (props, context) => <div style={getStyle(context)}>{props.name}</div>
They can define defaultProps, propTypes and contextTypes
Greeting.propTypes = { name: PropTypes.string.isRequired } Greeting.defaultProps = { name: "Guest" } Greeting.contextTypes = { color: PropTypes.string }
JSX Spread Attributes
Attribute distribution is a JSX fitch. Such a syntactic twist to pass all properties of an object as JSX attributes
These two examples are equivalent:
- props are written as attributes:
<main className="main" role="main">{children}</main>
- props "distributed" from the object:
<main {...{className: "main", role: "main", children}} />
Use props redirection to the object being created.
const FancyDiv = props => <div className="fancy" {...props} />
Now you can be sure that the desired attribute will be present (className), as well as those that you did not specify directly in the function but passed into it along with props
<FancyDiv data-id="my-fancy-div">So Fancy</FancyDiv>
Result: <div className="fancy" data-id="my-fancy-div">So Fancy</div>
Keep in mind that order matters. if props.className is defined, then this property will overwrite the className defined in FancyDiv
<FancyDiv className="my-fancy-div" />
Result: <div className="my-fancy-div"></div>
We can make FancyDivs className always “win” by typing it after the spread props ({... props}).
You can make your property always overwrite the transmitted via props.
const FancyDiv = props => <div {...props} className="fancy" />
There is a more elegant approach - to combine both properties.
const FancyDiv = ({ className, ...props }) => <div className={["fancy", className].join(' ')} {...props} />
Destructuring Arguments
The destructuring assignment is a feature of the ES2015 standard. It goes well with props for Simple Components.
These examples are equivalent.
const Greeting = props => <div>Hi {props.name}!</div> const Greeting = ({ name }) => <div>Hi {name}!</div>
The syntax of the rest (...) operator allows you to collect the remaining properties in the object
const Greeting = ({ name, ...props }) => <div>Hi {name}!</div>
Further, this object can be used to pass non-selected properties further in the created component.
const Greeting = ({ name, ...props }) => <div {...props}>Hi {name}!</div>
Avoid forwarding non-DOM props to composed components. If you can create an object without component-specific props.
Conditional rendering
You can use the usual if / else syntax in components. But conditional (ternary) operators are your friends.
if {condition && <span>Rendered when `truthy`</span> }
unless {condition || <span>Rendered when `falsey`</span> }
if-else (tidy one-liners) {condition ? <span>Rendered when `truthy`</span> : <span>Rendered when `falsey`</span> }
if-else (big blocks) {condition ? ( <span> Rendered when `truthy` </span> ) : ( <span> Rendered when `falsey` </span> )}
* I prefer not to use the constructs from the last example; in this case, using the usual if / else will be much clearer, although everything depends on the specific code.
Children types
React can render descendants of any type. Basically it is an array or string.
Line <div> Hello World! </div>
Array <div> {["Hello ", <span>World</span>, "!"]} </div>
Functions can also be used as children. However, you need to coordinate their behavior with the parent component.
Function <div> {() => { return "hello world!"}()} </div>
Array as children
Using an array of descendants is a common pattern, for example, so you make lists in React.
Use map () to create an array of React elements for each value in the array.
<ul> {["first", "second"].map((item) => ( <li>{item}</li> ))} </ul>
This is equivalent to an array literal with objects.
<ul> {[ <li>first</li>, <li>second</li>, ]} </ul>
Such a pattern can be used in conjunction with destructuring, attribute distribution, and other features to make writing code easier.
<ul> {arrayOfMessageObjects.map(({ id, ...message }) => <Message key={id} {...message} /> )} </ul>
Function as children
Using functions as descendants requires additional attention on your part so that you can benefit from them.
<div>{() => { return "hello world!»}()}</div>
However, they can give your components super strength, such a technique is usually called a render-callback.
This powerful technique is used in libraries such as ReactMotion. When you apply it, the rendering logic can be controlled from the parent component, instead of completely transferring it to the component itself.
Render callback
Here is an example of a component that uses render callback. It is generally useless, however it is a good illustration of the possibilities to begin with.
const Width = ({ children }) => children(500)
The component calls the descendants as a function with a specific argument. In this case, the number is 500.
To use this component, we pass it a function as a child.
<Width> {width => <div>window is {width}</div>} </Width>
We get this result
<div>window is 500</div>
With this approach, you can use the parameter (width), for conditional rendering
<Width> {width => width > 600 ? <div>min-width requirement met!</div> : null } </Width>
If we plan to use this condition many times, then we can define another component in order to pass this logic to it.
const MinWidth = ({ width: minWidth, children }) => <Width> {width => width > minWidth ? children : null } </Width>
Obviously, the static component Width is not very useful, but we can monitor the size of the browser window with this approach, this is something
class WindowWidth extends React.Component { constructor() { super() this.state = { width: 0 } } componentDidMount() { this.setState( {width: window.innerWidth}, window.addEventListener( "resize", ({ target }) => this.setState({width: target.innerWidth}) ) ) } render() { return this.props.children(this.state.width) } }
Many prefer High Order Components for this type of functionality. This is a matter of personal preference.
Children pass-through
You can create a component to apply the context and render the children.
class SomeContextProvider extends React.Component { getChildContext() { return {some: "context"} } render() {
Here you should make a decision. Wrap the descendants in another html tag (div), or return only the descendants. The first option can affect existing markup and can break styles. The second one will lead to an error (you remember that only one parent element from the component can be returned)
Option 1: additional div return <div>{children}</div>
Option 2: Error return children
It is best to manage the descendants with the help of special methods - React.Children. For example, the example below allows you to return only descendants and does not require additional wrappers.
return React.Children.only(this.props.children)
Proxy component
(I'm not sure that this name means anything at all) approx. author of the article
Buttons everywhere in the application. And each of them must have an attribute of type 'button'
<button type=«button">
Writing such books hundreds of times with pens is not our method. We can write a higher level component to redirect props to a component at a lower level.
const Button = props => <button type="button" {…props}>
Then you simply use the Button, instead of the button, and you can be sure that the desired attribute will be present in each button.
<Button />
<Button className="CTA">Send Money</Button> // <button type="button" class="CTA">Send Money</button>
Style component
This is a proxy component applied to styles. Let's say we have a button. She uses classes to look like 'primary'.
<button type="button" className="btn btn-primary»>
You can stir up this using a few simple components.
const PrimaryBtn = props => <Btn {...props} primary /> const Btn = ({ className, primary, ...props }) => <button type="button" className={classnames( "btn", primary && "btn-primary", className )} {...props} />
This will help visualize what is happening.
PrimaryBtn ()
↳ Btn ({primary: true})
↳ Button ({className: "btn btn-primary"}, type: "button"})
<'<Button type = "button" class = "btn btn-primary">'
Using these components will return the same result.
<PrimaryBtn /> <Btn primary /> <button type="button" className="btn btn-primary" />
This approach can bring tangible benefits, as it isolates certain styles in a particular component.
Event switch
When writing event handlers, we usually use function naming convention
handle{eventName} handleClick(e) { }
For components that handle multiple events, this name may be unnecessarily repeated. The names themselves do not give us any value, as they simply direct us to actions / functions.
handleClick() { require("./actions/doStuff")() } handleMouseEnter() { this.setState({ hovered: true }) } handleMouseLeave() { this.setState({ hovered: false }) }
Let's write a simple handler for all events with a switch by event type (event.type)
handleEvent({type}) { switch(type) { case "click": return require("./actions/doStuff")() case "mouseenter": return this.setState({ hovered: true }) case "mouseenter": return this.setState({ hovered: false }) default: return console.warn(`No case for event type "${type}"`) } }
You can also call function arguments directly using the arrow function.
<div onClick={() => someImportedAction({ action: "DO_STUFF" })}
Do not worry about performance optimization, until you encounter such problems. Seriously, no need.
* Personally, I do not think this approach is successful, since it does not add readability to the code. I prefer to use React features with functions that are bound to the context by the automaton. That is, the following notation is no longer necessary.
this.handleClick = this.handleClick.bind(this)
the following notation works instead
handleClick = () => {…}
and then somewhere else maybe just
onClick={this.handleClick}
In this case, the context (this) will not be lost if inside the function the handler refers to it. Accordingly, such functions can be easily transferred as props to other components and called into them.
Also, in case we pass such a function to a child, Simple Component, we can get a reference to the DOM element of this component via the event.target in the parent component, which is sometimes useful.
class SomeComponent extends React.Component { onButtonClick = (e) => { const button = e.target;
Layout component
Layout components are something like static DOM elements. Soon all, they will not be updated frequently, if at all.
Consider a component that contains two components horizontally.
<HorizontalSplit leftSide={<SomeSmartComponent />} rightSide={<AnotherSmartComponent />} />
We can aggressively optimize its performance.
Since HorizontalSplit will be the parent component for both components, it will never become their owner. We can say that it is never updated, while not breaking through the life cycles of internal components.
class HorizontalSplit extends React.Component { shouldComponentUpdate() { return false } render() { <FlexContainer> <div>{this.props.leftSide}</div> <div>{this.props.rightSide}</div> </FlexContainer> }
Container component
“A container doesn’t have a corresponding sub-component. That's it. ”- Jason BontaGiven: component CommentList, which is used several times in the application.
const CommentList = ({ comments }) => <ul> {comments.map(comment => <li>{comment.body}-{comment.author}</li> )} </ul>
We can create a new component responsible for receiving data and rendering the component CommentList
class CommentListContainer extends React.Component { constructor() { super() this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: comments => this.setState({comments: comments}); }) } render() { return <CommentList comments={this.state.comments} /> } }
We can write different container components for different application contexts.
Higher-order component
A higher order function is a function that can take other functions as arguments and / or return functions. No more complicated than this definition. So what are the higher order components?
You are already using container components; they are just containers wrapped in a function. Let's start with a simple greeting component.
const Greeting = ({ name }) => { if (!name) { return <div>Connecting...</div> } return <div>Hi {name}!</div> }
If he gets props.name, he will render the data. Otherwise, it will render “Connecting ...”. Now a little higher order:
const Connect = ComposedComponent => class extends React.Component { constructor() { super() this.state = { name: "" } } componentDidMount() {
This is the function that returns the component that renders the component that we passed as an argument (ComposedComponent)
Next we wrap the compote into this function.
const ConnectedMyComponent = Connect(Greeting)
This is a very powerful template so that a component can receive data and distribute it to any number of simple components.
Links (all in English):