- Create components patterns
- Create Class
- Extend Component
- Functional Component
- There are even more...
- High Order Components (HoC)
- React Anti-Patterns
import React from 'react';
var Greeting = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
import React, { Component } from 'react';
class Button extends Component {
render(){
return (<button></button>);
}
}
import React, { Component } from 'react';
class Button extends Component {
render(){
return (<button></button>);
}
}
class ExtraButton extends Button {
// override base render
render(){
return (<button className="cta-button"></button>);
}
}
// Button.js
export default props => {
return (
<buttonn onClick={ e => this.props.onClick(e) }></buttonn>}>{ props.text }</button>
);
}
Normally presentational components should not hold business logic. Business logic functionallity should come from Parent or High Order Components.
Parent here has some presentational functionallity.
import React, { Component } from 'react';
class Button extends Component {
render(){
return (<button onClick={ e => this.props.onClick(e)}>{ this.props.text }</button>);
}
}
class Configurator extends Component {
saveDesign(){
...
}
sendEmail(){
...
}
render(){
return (
<div className="configurator">
<Button text="Save Design" onClick={ () => this.saveDesign() }></Button>
<Button text="Send Email" onClick={ () => this.sendEmail() }></Button>
</div>
);
}
}
Parent here has some presentational functionallity.
import React, { Component } from 'react';
class Canvas extends Component {
render(){
return (<canvas onClick={ this.getMousePositionInsideContainer }></canvas>);
}
}
class ClickEvents extends Component {
getMousePositionInsideContainer(){
...
}
render(){
return React.cloneElement(this.props.children, {
getMousePositionInsideContainer: this.getMousePositionInsideContainer
})
}
}
}
// We use it like
class Configurator extends Component {
render(){
return (
<ClickEvents>
<Canvas />
</ClickEvents>
)
}
}
class Fetch extends Component {
render() {
// See how we're calling the child as a function?
// ↓
return this.props.children()
}
}
class Configurator extends Component {
render(){
return (
<Fetch url="api.myself.com">
{(result) => <p>{result}</p>}
</Fetch>
)
}
}
React.Children.map/forEach are similar to Array.map/forEach, but they can loop over functions/objects/DOMNodes (they dont throw).
class List extends Component {
render(){
return (
<div className="product-list">
{React.Children.map(children, (child, i) => {
// Ignore the first child
if (i < 1) return
return child;
})}
</div>
)
}
}
Ofcourse we can create simple functions that can enchace components
const pure = component => {
component.prototype.shouldComponentUpdate = (nextProps){
return !_.isEqual(nextProps, this.props);
}
}
class Button extends Component{ ... }
export default pure(component);
const isReactComponent = function( target, func )
{
if ( !target || !( target.prototype instanceof Component ) )
{
throw new StoreError( {
message : 'connect() should wrap a React Component',
context : Component.prototype,
} );
}
func.call( this, target );
};
export const connect = ( ...args ) => ( target ) => isReactComponent( target, ( target ) =>
{
const obj = Object.create( target.prototype );
const willMount = obj.componentWillMount || noop;
const willUnmount = obj.componentWillUnmount || noop;
const onComplete = obj.onComplete || noop;
const onError = obj.onError || ( stg => { throw new StoreError( stg ); } );
/**
* It is called when the Subjects stops pushing values
*/
target.prototype.onComplete = function()
{
onComplete.call( this );
};
/**
* It is called when an error happens on the store
* @param {Error object} err
*/
target.prototype.onError = function( err )
{
/* isStopped is passed by rx and means that the stream has
stopped due to an error, in that case err.message is not so
meaningfull and its better to pass the stack as message,
that can could change in the future */
const message = this.isStopped ? err.stack : err.message;
const settings = {
...err,
message : message || 'An error in your store.',
context : this,
};
onError.call( this, settings );
};
/**
* Set the listener on componentWillMount
* Listener will setState
*/
target.prototype.componentWillMount = function()
{
this.__subscription = stateSubject
.map( state => args.length ? pick( state, args ) : state )
.subscribe( state => this.setState( state ),
this.onError,
this.onComplete );
willMount.call( this );
};
/**
* Remove subscriber on componentWillUnmount
*/
target.prototype.componentWillUnmount = function()
{
this.unsubscribe();
willUnmount.call( this );
};
/**
* Provide an unsubscribe function to the underline component
*/
target.prototype.unsubscribe = function()
{
try
{
this.__subscription.dispose();
}
catch ( err )
{
this.onError( err );
}
};
return target;
} );
// And we use it like
@connect( 'hello' )
class MyHelloWorldComponent extends Component
{
render(){
return <div>{ this.state.hello }</div>;
}
}
// And we use it like
@multiInherit( ClickOutside, OnViewPort )
class MyHelloWorldComponent extends Component
{
...
}
Ofcourse same can be achieved doing
<ClickOutside>
<OnViewPort>
<MyHelloWorldComponent/>
</OnViewPort>
</ClickOutside>
- Checking if a component is mounted using
isMounted
(will be soon deprecated) - Calling
setState
incomponentDidMount
, triggers unessecery re-rendering. You can do it incomponentWillMount
- Mutating DOM directly, outside of React's lifecycle using
jQuery
or even plain javascript query selectors. - Prefer composition instead of inheritance, to avoid deep hierarchy trees
- Attaching to the
this.state
props that are not related to the rendering. Attach them tothis
directly if needed. - Using
bind
or() =>
functions inside the JSX. You can not always avoid it (e.g. when you usemap
) but usually you can. It should be avoided because it creates new function on every rendering having performance impact. Prefer the@bindComponent
or@autobind
decorators. Also do not do
class MyComponent extends Component {
doSomething = () => {
....
}
render(){
return(<button onClick={ this.doSomething }></button>)
}
}
Although this works and it is not creating new function, it is not equivalent of using bind
.
class T {
something = () => {}
somethingElse(){}
}
console.log(T.prototype.something) // undefined
console.log(T.prototype.somethingElse) // function{...}
It is treated as property and is created again and again per instance instead of being attached to the prototype. Its like doing:
class T {
constructor() {
this.something = () => ...
}
}
It means that you can not use the prototype to test it (if needed).