HIGHER-ORDER
COMPONENTS
Ilya Gelman
Ilya Gelman
- Organizer of AngularJS-IL & ReactJS-Israel
- Organizer of ReactNext 2016
- Author of The Complete Redux Book
Consultant @ 500Tech
JS
Functions are
first-class citizens
wut?
function square(number) {

return number * number;

}



var square = function (number) {

return number * number;

};
console.log(square);



const square = (number) => number * number;



array.map(square);
function square(number) {

return number * number;

}



var square = function (number) {

return number * number;

};
console.log(square);



const square = (number) => number * number;



array.map(square);
function square(number) {

return number * number;

}



var square = function (number) {

return number * number;

};
console.log(square);



const square = (number) => number * number;



array.map(square);
function square(number) {

return number * number;

}



var square = function (number) {

return number * number;

};
console.log(square);



const square = (number) => number * number;



array.map(square);
HIGHER-ORDER
HIGHER-ORDER
const a = () => { ... }

const b = (f) => { ... }



b(a);
const c = () => {

return () => {

...

}

}
() => () => () => () => (
const awesomeMiddleware =
({ dispatch, getState }) =>
(next) =>
(action) => { ... }
const awesomeMiddleware =
({ dispatch, getState }) =>
(next) =>
(action) => { ... }
HIGHER-ORDER
COMPONENT
class BaseComponent extends React.Component { ... }



class InputComponent extends BaseComponent { ... }



class Checkbox extends InputComponent { ... }
COMPOSITION
VS.
INHERITANCE
COMPOSITION VS. INHERITANCE
What things do What things are
Extend as you need Predict the future
Separated Tightly coupled
Startup Enterprise
THE
BANANA/GORILLA
PROBLEM
class Checkbox extends React.Component {


constructor() {

Object.asign(this, {

validate(...),

enlargeOnMobile(),

disableIf(...)

})

}
}

STATELESS
COMPONENTS
Available from React 0.14+
const DropDown = ({ list = [] }) => (

<div className="dropdown">

<ul>

{ list.map((item) => <li>{ item }</li>) }

</ul>

</div>

);
const DropDown = ({ list = [] }) => (

<div className="dropdown">

<ul>

{ list.map((item) => <li>{ item }</li>) }

</ul>

</div>

);
const loadOptions = (Component) =>

class LoadOptions extends React.Component {

constructor() {

super();



this.state = {

loaded: false,

list: null

}

}



componentDidMount() {

API.fetchOptions().then(

(list) =>

this.setState({ loaded: true, list })

);

}



render() {

return this.state.loaded &&

<Component list={ this.state.list }/>;

}

};
const loadOptions = (Component) =>

class LoadOptions extends React.Component {

constructor() {

super();



this.state = {

loaded: false,

list: null

}

}



componentDidMount() {

API.fetchOptions().then(

(list) =>

this.setState({ loaded: true, list })

);

}



render() {

return this.state.loaded &&

<Component list={ this.state.list }/>;

}

};
const loadOptions = (Component) =>

class LoadOptions extends React.Component {

constructor() {

super();



this.state = {

loaded: false,

list: null

}

}



componentDidMount() {

API.fetchOptions().then(

(list) =>

this.setState({ loaded: true, list })

);

}



render() {

return this.state.loaded &&

<Component list={ this.state.list }/>;

}

};
const loadOptions = (Component) =>

class LoadOptions extends React.Component {

constructor() {

super();



this.state = {

loaded: false,

list: null

}

}



componentDidMount() {

API.fetchOptions().then(

(list) =>

this.setState({ loaded: true, list })

);

}



render() {

return this.state.loaded &&

<Component list={ this.state.list }/>;

}

};
const loadOptions = (Component) =>

class LoadOptions extends React.Component {

constructor() {

super();



this.state = {

loaded: false,

list: null

}

}



componentDidMount() {

API.fetchOptions().then(

(list) =>

this.setState({ loaded: true, list })

);

}



render() {

return this.state.loaded &&

<Component list={ this.state.list }/>;

}

};
const loadOptions = (Component) =>

class LoadOptions ...
const loadOptions = (Component) => {

class LoadOptions ...
return React.createElement(LoadOptions);
}
const DropDown = (...) => (...);
const LazyDropDown = loadOptions(DropDown);



const App = () => (

<div>

<LazyDropDown />

</div>

);
const DropDown = (...) => (...);
const App = () => (

<div>

{ loadOptions(DropDown) }

</div>

);
const logProps = (Component) => {

console.log(Component.props);



return Component;

};
const DropDown = (...) => (...);
const App = () => (

<div>

{ logProps(<DropDown list={ … } />) }

</div>

);
DECORATOR
npm i recompose -S
react-next.com
import { withState } from 'recompose';



const DropDown = ({ list, visible, setVisibility }) => (

<div className="dropdown"

onClick={ () => setVisibility(visible => !visible) }>

{ visible && <ul>/* { ... } */</ul> }

</div>

);



export default withState(

'visible', // value on state

'setVisibility', // callback to change the state

false // initial value

)(DropDown);
import { withState } from 'recompose';



const DropDown = ({ list, visible, setVisibility }) => (

<div className="dropdown"

onClick={ () => setVisibility(visible => !visible) }>

{ visible && <ul>/* { ... } */</ul> }

</div>

);



export default withState(

'visible', // value on state

'setVisibility', // callback to change the state

false // initial value

)(DropDown);
import { withState } from 'recompose';



const DropDown = ({ list, visible, setVisibility }) => (

<div className="dropdown"

onClick={ () => setVisibility(visible => !visible) }>

{ visible && <ul>/* { ... } */</ul> }

</div>

);



export default withState(

'visible', // value on state

'setVisibility', // callback to change the state

false // initial value

)(DropDown);
import { withState } from 'recompose';



const DropDown = ({ list, visible, setVisibility }) => (

<div className="dropdown"

onClick={ () => setVisibility(visible => !visible) }>

{ visible && <ul>/* { ... } */</ul> }

</div>

);



export default withState(

'visible', // value on state

'setVisibility', // callback to change the state

false // initial value

)(DropDown);
import { withState, mapProps, pure } from 'recompose';



...



withState(..., mapProps(..., pure(MyComponent)));
pick(unique(flatten(map(array))));
))))))));
pick(unique(flatten(map(array))));



compose(

pick,

unique,

flatten,

map

)(array);
function compose(...fns) {

const lastFn = fns[fns.length - 1]

const restFns = fns.slice(0, -1)

return (target) =>
restFns.reduceRight(
(composed, f) => f(composed),
lastFn(target)
)
}
function compose(...fns) {

const lastFn = fns[fns.length - 1]

const restFns = fns.slice(0, -1)

return (target) =>
restFns.reduceRight(
(composed, f) => f(composed),
lastFn(target)
)
}
function compose(...fns) {

const lastFn = fns[fns.length - 1]

const restFns = fns.slice(0, -1)

return (target) =>
restFns.reduceRight(
(composed, f) => f(composed),
lastFn(target)
)
}
function compose(...fns) {

const lastFn = fns[fns.length - 1]

const restFns = fns.slice(0, -1)

return (target) =>
restFns.reduceRight(
(composed, f) => f(composed),
lastFn(target)
)
}
– Prefer composition over inheritance
– Use stateless components
– npm install recompose
– Don’t miss ReactNext 2016
Read our blog:
http://blog.500tech.com
Ilya Gelman
ilya@500tech.com

Higher-Order Components — Ilya Gelman

  • 1.
  • 2.
    Ilya Gelman - Organizerof AngularJS-IL & ReactJS-Israel - Organizer of ReactNext 2016 - Author of The Complete Redux Book Consultant @ 500Tech
  • 3.
  • 4.
  • 5.
    function square(number) {
 returnnumber * number;
 }
 
 var square = function (number) {
 return number * number;
 }; console.log(square);
 
 const square = (number) => number * number;
 
 array.map(square);
  • 6.
    function square(number) {
 returnnumber * number;
 }
 
 var square = function (number) {
 return number * number;
 }; console.log(square);
 
 const square = (number) => number * number;
 
 array.map(square);
  • 7.
    function square(number) {
 returnnumber * number;
 }
 
 var square = function (number) {
 return number * number;
 }; console.log(square);
 
 const square = (number) => number * number;
 
 array.map(square);
  • 8.
    function square(number) {
 returnnumber * number;
 }
 
 var square = function (number) {
 return number * number;
 }; console.log(square);
 
 const square = (number) => number * number;
 
 array.map(square);
  • 9.
  • 10.
  • 11.
    const a =() => { ... }
 const b = (f) => { ... }
 
 b(a); const c = () => {
 return () => {
 ...
 }
 }
  • 12.
    () => ()=> () => () => (
  • 13.
    const awesomeMiddleware = ({dispatch, getState }) => (next) => (action) => { ... }
  • 14.
    const awesomeMiddleware = ({dispatch, getState }) => (next) => (action) => { ... }
  • 16.
  • 17.
    class BaseComponent extendsReact.Component { ... }
 
 class InputComponent extends BaseComponent { ... }
 
 class Checkbox extends InputComponent { ... }
  • 18.
  • 19.
    COMPOSITION VS. INHERITANCE Whatthings do What things are Extend as you need Predict the future Separated Tightly coupled Startup Enterprise
  • 20.
  • 21.
    class Checkbox extendsReact.Component { 
 constructor() {
 Object.asign(this, {
 validate(...),
 enlargeOnMobile(),
 disableIf(...)
 })
 } }

  • 22.
  • 23.
    const DropDown =({ list = [] }) => (
 <div className="dropdown">
 <ul>
 { list.map((item) => <li>{ item }</li>) }
 </ul>
 </div>
 );
  • 24.
    const DropDown =({ list = [] }) => (
 <div className="dropdown">
 <ul>
 { list.map((item) => <li>{ item }</li>) }
 </ul>
 </div>
 );
  • 25.
    const loadOptions =(Component) =>
 class LoadOptions extends React.Component {
 constructor() {
 super();
 
 this.state = {
 loaded: false,
 list: null
 }
 }
 
 componentDidMount() {
 API.fetchOptions().then(
 (list) =>
 this.setState({ loaded: true, list })
 );
 }
 
 render() {
 return this.state.loaded &&
 <Component list={ this.state.list }/>;
 }
 };
  • 26.
    const loadOptions =(Component) =>
 class LoadOptions extends React.Component {
 constructor() {
 super();
 
 this.state = {
 loaded: false,
 list: null
 }
 }
 
 componentDidMount() {
 API.fetchOptions().then(
 (list) =>
 this.setState({ loaded: true, list })
 );
 }
 
 render() {
 return this.state.loaded &&
 <Component list={ this.state.list }/>;
 }
 };
  • 27.
    const loadOptions =(Component) =>
 class LoadOptions extends React.Component {
 constructor() {
 super();
 
 this.state = {
 loaded: false,
 list: null
 }
 }
 
 componentDidMount() {
 API.fetchOptions().then(
 (list) =>
 this.setState({ loaded: true, list })
 );
 }
 
 render() {
 return this.state.loaded &&
 <Component list={ this.state.list }/>;
 }
 };
  • 28.
    const loadOptions =(Component) =>
 class LoadOptions extends React.Component {
 constructor() {
 super();
 
 this.state = {
 loaded: false,
 list: null
 }
 }
 
 componentDidMount() {
 API.fetchOptions().then(
 (list) =>
 this.setState({ loaded: true, list })
 );
 }
 
 render() {
 return this.state.loaded &&
 <Component list={ this.state.list }/>;
 }
 };
  • 29.
    const loadOptions =(Component) =>
 class LoadOptions extends React.Component {
 constructor() {
 super();
 
 this.state = {
 loaded: false,
 list: null
 }
 }
 
 componentDidMount() {
 API.fetchOptions().then(
 (list) =>
 this.setState({ loaded: true, list })
 );
 }
 
 render() {
 return this.state.loaded &&
 <Component list={ this.state.list }/>;
 }
 };
  • 30.
    const loadOptions =(Component) =>
 class LoadOptions ... const loadOptions = (Component) => {
 class LoadOptions ... return React.createElement(LoadOptions); }
  • 31.
    const DropDown =(...) => (...); const LazyDropDown = loadOptions(DropDown);
 
 const App = () => (
 <div>
 <LazyDropDown />
 </div>
 );
  • 32.
    const DropDown =(...) => (...); const App = () => (
 <div>
 { loadOptions(DropDown) }
 </div>
 );
  • 33.
    const logProps =(Component) => {
 console.log(Component.props);
 
 return Component;
 };
  • 34.
    const DropDown =(...) => (...); const App = () => (
 <div>
 { logProps(<DropDown list={ … } />) }
 </div>
 );
  • 35.
  • 36.
  • 37.
  • 38.
    import { withState} from 'recompose';
 
 const DropDown = ({ list, visible, setVisibility }) => (
 <div className="dropdown"
 onClick={ () => setVisibility(visible => !visible) }>
 { visible && <ul>/* { ... } */</ul> }
 </div>
 );
 
 export default withState(
 'visible', // value on state
 'setVisibility', // callback to change the state
 false // initial value
 )(DropDown);
  • 39.
    import { withState} from 'recompose';
 
 const DropDown = ({ list, visible, setVisibility }) => (
 <div className="dropdown"
 onClick={ () => setVisibility(visible => !visible) }>
 { visible && <ul>/* { ... } */</ul> }
 </div>
 );
 
 export default withState(
 'visible', // value on state
 'setVisibility', // callback to change the state
 false // initial value
 )(DropDown);
  • 40.
    import { withState} from 'recompose';
 
 const DropDown = ({ list, visible, setVisibility }) => (
 <div className="dropdown"
 onClick={ () => setVisibility(visible => !visible) }>
 { visible && <ul>/* { ... } */</ul> }
 </div>
 );
 
 export default withState(
 'visible', // value on state
 'setVisibility', // callback to change the state
 false // initial value
 )(DropDown);
  • 41.
    import { withState} from 'recompose';
 
 const DropDown = ({ list, visible, setVisibility }) => (
 <div className="dropdown"
 onClick={ () => setVisibility(visible => !visible) }>
 { visible && <ul>/* { ... } */</ul> }
 </div>
 );
 
 export default withState(
 'visible', // value on state
 'setVisibility', // callback to change the state
 false // initial value
 )(DropDown);
  • 42.
    import { withState,mapProps, pure } from 'recompose';
 
 ...
 
 withState(..., mapProps(..., pure(MyComponent)));
  • 43.
  • 44.
  • 46.
  • 47.
    function compose(...fns) {
 constlastFn = fns[fns.length - 1]
 const restFns = fns.slice(0, -1)
 return (target) => restFns.reduceRight( (composed, f) => f(composed), lastFn(target) ) }
  • 48.
    function compose(...fns) {
 constlastFn = fns[fns.length - 1]
 const restFns = fns.slice(0, -1)
 return (target) => restFns.reduceRight( (composed, f) => f(composed), lastFn(target) ) }
  • 49.
    function compose(...fns) {
 constlastFn = fns[fns.length - 1]
 const restFns = fns.slice(0, -1)
 return (target) => restFns.reduceRight( (composed, f) => f(composed), lastFn(target) ) }
  • 50.
    function compose(...fns) {
 constlastFn = fns[fns.length - 1]
 const restFns = fns.slice(0, -1)
 return (target) => restFns.reduceRight( (composed, f) => f(composed), lastFn(target) ) }
  • 51.
    – Prefer compositionover inheritance – Use stateless components – npm install recompose – Don’t miss ReactNext 2016
  • 52.