KEMBAR78
Advanced React Component Patterns - ReactNext 2018 | PDF
BEYOND CONTAINERS &
PRESENTATIONAL COMPONENTS
Advanced
Patterns
ROBERT HERBST
Johannesburg,
South Africa
JOURNEY
Data Down Actions Up
Unidirectional Data Flow
vs
Model Binding
Flux
Presentational
and Container
Components
“React bindings for Redux separate
presentational components from
container components. This
approach can make your app easier to
understand and allow you to more
easily reuse components.”
— Redux Documentation
Presentational
Components
Container Components
Purpose
How things look (markup,
styles)
How things work (data fetching, state
updates)
Aware of
Redux
No Yes
To read data Read data from props Subscribe to Redux state
To change
data
Invoke callbacks from props Dispatch Redux actions
Are written By hand Usually generated by React Redux
const MyGreeting = ({ name }) !=>
<h1>Hello {name}!</h1>;
const mapStateToProps = state !=> ({
name: state.name
});
export default connect(mapStateToProps)
(MyGreeting);
Container
Know things about outside (context)
Presentational
Have everything passed in (pure)
Class Component
Know things about outside (context)
Function Component
Have everything passed in (pure)
Hold on…
Class Components also store state.
That’s not “outside”?
Is state really inside the component?
How does the React lifecycle work?
class MyComponent extends
React.Component
Where is the class component
in Redux containers?
connect()
Higher Order Component
const hoc = WrappedComponent !=>
class HOC extends React.Component {
render() {
return <WrappedComponent !/>;
}
};
container
presentation
pure class
functionhigher order
component
noise
Pure Component
Side Effect Component
Pure Side Effect
Component
How did that component do that….
Victory Charts
Victory Charts
const data = […];
return (
<VictoryChart height={390}>
<VictoryLine data={data} !/>
<VictoryScatter
data={data}
size={5}
!/>
!</VictoryChart>
);
Victory Charts
const data = […];
return (
<VictoryChart height={390}>
<VictoryLine data={data} !/>
<VictoryScatter
data={data}
size={5}
!/>
!</VictoryChart>
);
Victory Charts
Victory Charts
This looks like
bi-directional communication
Data Down Actions Up
Unidirectional Data Flow
vs
Model Binding
<VictoryChart !/>
interrogates its children
<VictoryChart>
<VictoryLine … !/>
!</VictoryChart>
<VictoryChart
children={<VictoryLine … !/>}
!/>
Children Aware Component
Pure Side Effect
Component
Child Aware
Best Code Comment Award
Nominee
Wizard
<Wizard>
<Step>This is step one. Click next to continue!</Step>
<Step>This is the last step. Well done.!</Step>
!</Wizard>
export default class Wizard extends React.Component {
state = {
furthestStep: 0
};
completeStep = furthestStep !=> {
this.setState({ furthestStep });
};
render() {
const { children } = this.props;
return (
<React.Fragment>
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
]
, [])}
!</React.Fragment>
);
}
}
export default class Wizard extends React.Component {
state = {
furthestStep: 0
};
completeStep = furthestStep !=> {
this.setState({ furthestStep });
};
render() {
const { children } = this.props;
return (
<React.Fragment>
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
]
, [])}
!</React.Fragment>
);
}
}
state = {
furthestStep: 0
};
completeStep = furthestStep !=> {
this.setState({ furthestStep });
};
export default class Wizard extends React.Component {
state = {
furthestStep: 0
};
completeStep = furthestStep !=> {
this.setState({ furthestStep });
};
render() {
const { children } = this.props;
return (
<React.Fragment>
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
]
, [])}
!</React.Fragment>
);
}
}
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
],
[]
)}
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
],
[]
)}
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
],
[]
)}
{React.Children.toArray(children).reduce(
(acc, child, index) !=>
index > this.state.furthestStep
? acc
: [
!!...acc,
React.cloneElement(child, {
onComplete: () !=> this.completeStep(index + 1)
})
],
[]
)}
Children Enhancing Component
Pure Side Effect
Component
Child Aware
Child Enhancing
Framework Code,
Not Application Code!
Wizard
<Wizard>
<Step>This is step one. Click next to continue!</Step>
<Step>This is the last step. Well done.!</Step>
!</Wizard>
but not presentational
All Pure Components
Data In, HTML (UI) Out
How things look (markup, styles)
Presentational Components
Pure Side Effect
Component
Child Aware
Child Enhancing
Presentation
import Desktop from './MyComponent.desktop';
import Mobile from './MyComponent.mobile';
let MyComponent = Responsive({ Mobile, Desktop });
export default MyComponent;
export const MOBILE_QUERY = '(max-width: 767px)';
export const isMobile = () !=>
(window.matchMedia ? window.matchMedia(MOBILE_QUERY).matches : false);
!/* This assumes only two breakpoints. It can become more sophisticated !*/
export default ({ Mobile, Desktop }) !=>
class Responsive extends Component {
render() {
return (
<MediaQuery query={MOBILE_QUERY}>
{matches !=> (
matches ?
<Mobile {!!...this.props} !/> :
<Desktop {!!...this.props} !/>)}
!</MediaQuery>
);
}
};
Decision Component
Simplifies each case
Trade off:
Simplicity vs Dryness
Pure Side Effect
Component
Child Aware
Child Enhancing
Presentation Decision
Side Effect Components
React Context
const MyContext = React.createContext(defaultValue);
<MyContext.Provider value={!/* some value !*/}>
…
!</MyContext.Provider>
class MyComponent extends React.Component {
static contextType = MyContext;
}
<MyContext.Consumer>
{value !=> !/* render something based on the context value !*/}
!</MyContext.Consumer>
const Analytics = React.createContext();
const tracker = new AnalyticsService(config);
const el = document.querySelector(‘#mount-target');
ReactDOM.render(
<Analytics.Provider tracker={tracker}>
<App !/>
!</Analytics.Provider>,
el
);
class ExampleDownloadButton extends React.Component {
onClick = () !=> {
const { tracker } = this.props;
tracker.track('download.button.clicked');
};
render() {
return (
<Button onClick={this.onClick}>
<Icon name="cloud-download" size={16} !/>
Download
!</Button>
);
}
}
export default withTracker(ExampleDownloadButton);
Service Injection Component
Pure Side Effect
Component
Child Aware
Child Enhancing
Presentation Decision Service
Feature Toggle Component or
Authentication Component
Decision + Service Component
const NewComponent = () !=> (
<p>This is only for beta users!</p>
);
const OldComponent = () !=> (
<p>This is for normal users!</p>
);
export default withFeature('betaFeature')({
new: NewComponent,
old: OldComponent
});
import TheComponent from './the-component';
const Page = () !=> (
<div>
<TheComponent !/>
!</div>
);
const features = new FeatureService(config);
const el = document.querySelector(‘#mount-target');
ReactDOM.render(
<FeatureProvider features={features}>
<App !/>
!</FeatureProvider>,
el
);
Event Listener Component
Observables, high performance etc
redux
connect()
Pure Side Effect
Component
Child Aware
Child Enhancing
Presentation Decision Service Event Listener
💅 styled-components
const Card = styled.div`
max-width: 600px;
`;
<styled.div id=“my-component">
!</styled.div>
<style type="text/css" data-styled-components="gHctJy">
.gHctJy {
max-width: 600px;
}
!</style>
<div class="gHctJy">
My Card
!</div>
Pure Side Effect
Component
Child Aware
Child Enhancing
Presentation Decision Service Event Listener Styled
Why do developers care about patterns?
The Magical Number Seven,
Plus or Minus Two:
Some Limits on Our Capacity for
Processing Information
—one of the most highly cited
papers in psychology
How did that component do that….
reduce the number of times you ask
Pure Side Effect
Component
Child Aware
Child Enhancing
Presentation Decision Service Event Listener Styled
Why separate pure vs side effects?
Pure components are:
•Easier to reason about
•Easier to test
•Easier to compose
•Generally simpler
Functional Core,
Imperative Shell
— Gary Bernhardt
🔥

How do Hooks affect this?
import { useState } from 'react';
function Example() {
!// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times!</p>
<button onClick={() !=> setCount(count + 1)}>
Click me
!</button>
!</div>
);
}
Class Component
Know things about outside (context)
Pure Component
Have everything passed in (pure)
class component = side effects
Hooks = side effects
In real code bases the split is
almost never perfectly clear.



Hooks make side effects easier.
No reason to stop separating concerns.
Questions?

Advanced React Component Patterns - ReactNext 2018