Advanced React Component Patterns
by,
Shakthishree
Simple Toggle Component
import React, {Component} from 'react';
import Switch from './switch';
export default class Toggle extends Component {
state = {on: false};
toggle = () => {
this.setState(({on}) => ({on: !on}));
this.props.onToggle(!this.state.on);
};
render() {
return (
<div>
<Switch
on={this.state.on}
onClick={this.toggle}
/>
<div>
{this.state.on ? 'on' : 'off'}
</div>
</div>
);
}
}
import React, {Component} from 'react';
import Toggle from './toggle';
export default class User extends Component {
onToggle = ( on ) => {
console.log( on ? 'On' : 'Off' );
};
render() {
return (
<Toggle onToggle = { this.onToggle } />
)
}
}
Flexible Toggle Component
Compound Component
export default class Toggle extends Component {
state = {on: false};
static On = ({on}) =>
(on ? <div>On</div> : null);
static Off = ({on}) =>
(on ? null : <div>Off</div>);
static Button = ({on, toggle}) =>
<Switch on={on} onClick={toggle} />;
toggle = () => {
this.setState(({on}) => ({on: !on}));
this.props.onToggle(!this.state.on);
};
render() {
return (
React.Children.map(
this.props.children, child =>
React.cloneElement(child, {
on: this.state.on,
toggle: this.toggle,
});
);
);
}
}
export default class User extends Component {
onToggle = on => {
console.log(on ? 'on' : 'false' );
};
render() {
return (
<Toggle onToggle={this.onToggle}>
<Toggle.Button />
<Toggle.Off />
<Toggle.On />
</Toggle>
);
}
}
Compound Component
export default class Toggle extends Component {
state = {on: false};
static On = ({on, children}) =>
(on ? children : null);
static Off = ({on, children}) =>
(on ? null : children);
static Button = ({on, toggle}) =>
<Switch on={on} onClick={toggle} />;
toggle = () => {
this.setState(({on}) => ({on: !on}));
this.props.onToggle(!this.state.on);
};
render() {
return (
React.Children.map(
this.props.children, child =>
React.cloneElement(child, {
on: this.state.on,
toggle: this.toggle,
});
);
);
}
}
export default class User extends Component {
onToggle = on => {
console.log(on ? 'on' : 'false' );
};
render() {
return (
<Toggle onToggle={this.onToggle}>
<Toggle.Button />
<Toggle.Off>
<div>Off</div>
</Toggle.Off>
<Toggle.On>
<div>On</div>
</Toggle.On>
</Toggle>
);
}
}
Compound Component
React Context API
const ToggleContext = React.createContext();
static On = ({children}) =>
<ToggleContext.Consumer>
{({on, children}) => {
return on ? children : null;}}
</ToggleContext.Consumer>;
static Off = ({children}) =>
<ToggleContext.Consumer>
{({on, children}) => {
return on ? null : children;}}
</ToggleContext.Consumer>;
static Button = ({children}) =>
<ToggleContext.Consumer>
{({on, toggle}) =>
<Switch on={on} onClick={toggle} />}
</ToggleContext.Consumer>;
render() {
return (
<ToggleContext.Provider
value={{on: this.state.on,
toggle: this.toggle}}>
{this.props.children}
</ToggleContext.Provider>);}
export default class User extends Component {
onToggle = on => {
console.log(on ? 'on' : 'false' );
};
render() {
return (
<Toggle onToggle={this.onToggle}>
<div style={{
height: '40px',
width: '60px',
backgroundColor: '#7eb36d',
marginTop: '10px',
border: '1px solid grey',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}>
<Toggle.Button />
</div>
<Toggle.Off />
<Toggle.On />
</Toggle>
);
}
}
export default class Toggle extends Component {
state = {on: false};
static On = ({on, children}) =>
(on ? children : null);
static Off = ({on, children}) =>
(on ? null : children);
static Button = ({on, toggle}) =>
<Switch on={on} onClick={toggle} />;
toggle = () => {
this.setState(({on}) => ({on: !on}));
this.props.onToggle(!this.state.on);
};
render() {
return (
React.Children.map(
this.props.children, child =>
React.cloneElement(child, {
on: this.state.on,
toggle: this.toggle,
});
);
);
}
}
React Context API
function ToggleConsumer(props) {
return (
<ToggleContext.Consumer {...props}>
{context => {
if (!context) {
throw new Error(
`Compound components are used
outside provider`)}
return props.children(context)
}}
</ToggleContext.Consumer>
)}
static On = ({children}) => (
<ToggleConsumer>
{({on}) => (on ? children : null)}
</ToggleConsumer>)
static Off = ({children}) => (
<ToggleConsumer>
{({on}) => (on ? null : children)}
</ToggleConsumer>)
static Button = props => (
<ToggleConsumer>
{({on, toggle}) => (
<Switch on={on} onClick={toggle} {...props} />)}
</ToggleConsumer>)
export default class User extends Component {
onToggle = on => {
console.log(on ? 'on' : 'false' );
};
render() {
return (
<div onToggle={this.onToggle}>
<div style={{
height: '40px',
width: '60px',
backgroundColor: '#7eb36d',
marginTop: '10px',
border: '1px solid grey',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}>
<Toggle.Button />
</div>
<Toggle.Off />
<Toggle.On />
</div>
);
}
}
const ToggleContext = React.createContext();
static On = ({children}) =>
<ToggleContext.Consumer>
{({on, children}) => {
return on ? children : null;}}
</ToggleContext.Consumer>;
static Off = ({children}) =>
<ToggleContext.Consumer>
{({on, children}) => {
return on ? null : children;}}
</ToggleContext.Consumer>;
static Button = ({children}) =>
<ToggleContext.Consumer>
{({on, toggle}) =>
<Switch on={on} onClick={toggle} />}
</ToggleContext.Consumer>;
render() {
return (
<ToggleContext.Provider
value={{on: this.state.on,
toggle: this.toggle}}>
{this.props.children}
</ToggleContext.Provider>);}
HOC
function wrapper(Component, props) {
return (
<ToggleContext.Consumer {...props}>
{context => {
return <Component context={context} />;
}}
</ToggleContext.Consumer>
);
}
static On = ({children}) =>
wrapper(({on}) => (on ? children : null));
static Off = ({children}) =>
wrapper(({on}) => (on ? null : children));
static Button = ({children}) =>
wrapper(context => {
return <Switch
on={context.on}
onClick={context.context.toggle} />;
});
render() {
return (
<ToggleContext.Provider
value={{on: this.state.on,
toggle: this.toggle}}
{...this.props}
/>
);
render() {
return (
<Toggle onToggle={this.onToggle}>
<Toggle.Button />
<Toggle.Off>
<div>The button is off</div>
</Toggle.Off>
<Toggle.On>
<div>The button is on</div>
</Toggle.On>
</Toggle>
)
}
const ToggleContext = React.createContext();
static On = ({children}) =>
<ToggleContext.Consumer>
{({on, children}) => {
return on ? children : null;}}
</ToggleContext.Consumer>;
static Off = ({children}) =>
<ToggleContext.Consumer>
{({on, children}) => {
return on ? null : children;}}
</ToggleContext.Consumer>;
static Button = ({children}) =>
<ToggleContext.Consumer>
{({on, toggle}) =>
<Switch on={on} onClick={toggle} />}
</ToggleContext.Consumer>;
render() {
return (
<ToggleContext.Provider
value={{on: this.state.on,
toggle: this.toggle}}>
{this.props.children}
</ToggleContext.Provider>);}
Use case
HOC to handle state
import React,{Component, Fragment} from 'react';
import Toggle from './toggle';
class User extends Component{
render() {
return(
<Fragment>
<div>
{this.props.state.on ? 'On' : 'Off' }
</div>
<Switch
onReset={this.onReset}
on={this.props.state.on}
onClick={this.props.toggle}
/>
</Fragment>);
}
}
export default Toggle(User)
import React, {Component} from 'react';
function ToggleHOC ( Comp ){
return class Toggle extends Component {
state = {on: false};
toggle = () => {
this.setState({on: !this.state.on});
};
render() {
return <Comp {...this.props}
state={this.state}
toggle={this.toggle} />;
}
};
};
export default ToggleHOC;
Render Props
import React, {Component} from 'react';
import Switch from './switch';
class Toggle extends Component {
state = {on: false};
toggle = () => {
this.setState({on: !this.state.on});
this.props.onToggle(this.state.on);
};
render(){
return(
this.props.children({
on: this.state.on,
toggle: this.toggle,
})
)
}
}
import React, {Component} from 'react';
import './switch';
export default class User extends Component {
onToggle = on => {
console.log(on ? 'on' : 'false' );
};
render() {
return (
<Toggle
onToggle={this.onToggle}
>
{({on, toggle, reset}) =>
<div>
{on ? 'on' : 'off'}
</div>
<Switch on={on} onClick={toggle} />
}
</Toggle>
);
}
}
Control Props
import React, {Component} from 'react';
import Switch from './switch';
export default class Toggle extends Component {
render() {
return (
<div>
<Switch
on={this.props.on}
onClick={this.props.onToggle}
/>
<div>
{this.props.on ? 'on' : 'off'}
</div>
</div>
);
}
}
import React, {Component} from 'react';
import Toggle from './Toggle';
export default class User extends Component {
state = {on: false};
onToggle(){
this.setState({on: !this.state.on});
}
render(){
return(
<Toggle
on={this.state.on}
onToggle={this.onToggle.bind(this)};
)
}
State Initializers
import React, {Component} from 'react';
import Switch from './switch';
class Toggle extends Component {
static defaultProps = {
initialOn: false,
}
initialState = {on: this.props.initialOn ?
this.props.initialOn :
this.props.defaultProps.initialOn};
state = this.initialState;
toggle = () => {
this.setState({on: !this.state.on});
this.props.onToggle(this.state.on)
};
render(){
return(
this.props.children({
on: this.state.on,
toggle: this.toggle
})
)
}
}
import React, {Component} from 'react';
import './switch';
export default class User extends Component {
onToggle = on => {
console.log(on ? 'on' : 'false' );
};
render() {
return (
<Toggle
initialOn= {true}
onToggle={this.onToggle}
>
{({on, toggle, reset}) =>
<div>
{on ? 'on' : 'off'}
</div>
<Switch on={on} onClick={toggle} />
}
</Toggle>
);
}
}
Use Case
State Reducers
state = {on : false}
internalSetState(changes, callback) {
this.setState(state => {
const changesObject =
typeof changes === 'function' ?
changes(state) :
changes;
const reducedChanges =
this.props.stateReducer(state, changesObject)
|| {}
return Object.keys(reducedChanges).length
? reducedChanges
: null
}, callback)
}
toggle = () => {
this.internalSetState(
({on}) => ({on: !on}),
() => this.props.onToggle(this.state.on),
)
}
render(){
return(
this.props.children({
on: this.state.on,
toggle: this.toggle,
})
)
}
state = {timesClicked: 0}
toggleStateReducer = (state, changes) => {
return this.state.timesClicked >= 4 ?
{...changes, on: false}
: changes;
}
render() {
return (
<Toggle
stateReducer={this.toggleStateReducer}
initialOn= {true}
onToggle={this.onToggle}
>
{({on, toggle}) =>
<div> {on ? 'on' : 'off'} </div>
<Switch on={on} onClick={toggle} />
<div>
{this.state.timesClicked > 4 ?
<div> Whoa, you clicked too much! </div>
:
<div>
Click count:{this.state.timesClicked}
</div>
}
</div>
}
</Toggle>