Reputation:
Could anyone please help me understand how to use this Higher Order Component explained in the redux document?
I know what Higher Order Component is and I have used this patter several times but I can't figure out how to use this Higher Order Component.
Here is what it says...
Something needs to map the Immutable.JS props in your Smart Component to the pure JavaScript props used in your Dumb Component. That something is a Higher Order Component (HOC) that simply takes the Immutable.JS props from your Smart Component, and converts them using toJS() to plain JavaScript props, which are then passed to your Dumb Component.
Here is an example of such a HOC:
import React from 'react'
import { Iterable } from 'immutable'
export const toJS = WrappedComponent => wrappedComponentProps => {
const KEY = 0
const VALUE = 1
const propsJS = Object.entries(
wrappedComponentProps
).reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
wrappedComponentProp[VALUE]
)
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE]
return newProps
}, {})
return <WrappedComponent {...propsJS} />
}
And this is how you would use it in your Smart Component:
import { connect } from 'react-redux'
import { toJS } from './to-js'
import DumbComponent from './dumb.component'
const mapStateToProps = state => {
return {
// obj is an Immutable object in Smart Component, but it’s converted to a plain
// JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
// object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
// there is no issue with errant re-renderings.
obj: getImmutableObjectFromStateTree(state)
}
}
export default connect(mapStateToProps)(toJS(DumbComponent))
By converting Immutable.JS objects to plain JavaScript values within a HOC, we achieve Dumb Component portability, but without the performance hits of using toJS() in the Smart Component.
import React from 'react';
const DumbComponent = (props) => {
const {dataList} = props;
const renderList = (list) => {
return list.map((value) => {
return <li>value</li>
})
};
return (
<ul>
{renderList(dataList)}
</ul>
)
};
export default DumbComponent;
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DumbComponent from './DumbComponent';
//High Order Component
import toJS from './toJS';
class SmartComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<DumbComponent dataList={this.props.dataList} />
);
}
}
function mapStateToProps(states) {
return {
//Let's say this is immutable.
dataList: state.dataList,
};
}
//this is how I use Higher Order Component.
//export default connect(mapStateToProps)(SmartComponent);
export default connect(mapStateToProps)(toJS(DumbComponent));
export default connect(mapStateToProps)(toJS(DumbComponent));
This doesn't even export SmartComponent itself. How can the parent component of SmartComponent whose child is DumbComponent use SmartComponent if it is not even exported??
Please tell me how to use this Higher Order Component in the sample code I prepared for this post.
So, this is the correct way to write?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DumbComponent from '../components/DumbComponent';
class SmartComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<DumbComponent dataList={this.props.dataList} /> //immutable
);
}
}
function mapStateToProps(states) {
return {
dataList: state.dataList //immutable
};
}
export default connect(mapStateToProps)(SmartComponent);
import React from 'react';
import toJS from './higher_order_components/toJS';
const DumbComponent = (props) => {
const {dataList} = props;
const renderList = (list) => {
return list.map((value) => {
return <li>{value}</li>
})
};
return (
<ul>
{renderList(dataList)}
</ul>
)
};
export default toJS(DumbComponent);
Upvotes: 5
Views: 941
Reputation: 20614
When you do something like:
let Component = connect(mapStateToProps)(OtherComponent)
Component
is "smart" because it has access to the store. What you're doing in your code is doubling up on smart components.
class OtherSmartComponent {
render { return <DumbComponent {...smartProps} /> }
}
let SmartComponent = connect(mapStateToProps)(OtherSmartComponent)
That's why in the example Smart Component there is no intermediate Smart Component, just connect
, mapStateToProps
and DumbComponent
.
Might be worth unraveling it all:
import DumbComponent from './DumbComponent'
import toJS from './toJS'
let DumbComponentWithJSProps = toJS(DumbComponent)
let SmartComponent = connect(mapStateToProps)(DumbComponentWithJSProps)
export default SmartComponent
So really, in your code, SmartComponent isn't really smart. It's just a dumb component that renders another dumb component. The terminology makes this comment seem super harsh. 🤔
To clarify your comment:
If you want a component between your connected one and the one that's run through toJS
, then do that. No need to call connect
twice:
// Dumb.js
import toJS from './toJS
class Dumb { .. }
export default toJS(Dumb)
-----------------------------------
// Smart.js
import Dumb from './Dumb'
class Smart {
..methods..
render() { return <Dumb {...props} /> }
}
export default connect(mapStateToProps)(Smart)
Upvotes: 1