Recently I’ve started using React and Redux to rebuild one of the major components on the Gogobot website.
In order to understand the problem better, here are some screenshots of one of the components on that page.
Without really understanding the ins and outs of the component, you can see that the components share many things:
- Count label that is shown/hidden based on the number of items I selected from the filter.
- Drop down that opens on click of the icon.
- Once clicked, it sends an event (action) and requests the data from the
- Once clicked, it sends an event (action) and requests the data from the server based on the new filter.
Sitting down to design this component I realized that the **behavior** shared between each of them calls for a **higher order component** that will define the behavior and wrap all “child” components.
Doing this with pure React is fairly obvious so I’m not gonna go into that, this post is gonna be about doing this with Redux since the higher-order-component is connected to the dispatch, store and state.
Implementation
// src/components/Filters/FilterWrapper/index.jsimport React, { Component } from ‘react’;
import { bindActionCreators } from ‘redux’;
import { connect } from ‘react-redux’;function FilterWrapper(ComposedFilter, filterInfo) {
class BaseFilter extends Component {
constructor() {
super();
this.state = {
count: 0
};
this.onCheckboxChange = this.onCheckboxChange.bind(this);
}onClick(e) {
}onCheckboxChange(e) {
}render() {
let countLabel = this.state.count > 0 ?
<span>{ this.state.count }</span> :
null;return(
<div className=”filterDetailsWrapper”>
<div className=”filterTotalCount”>
{ countLabel }
</div>
<div className=”optionsDropDownContainer”>
<ComposedFilter
{…this.state}
{…this.props}
onCheckboxChange={ this.onCheckboxChange }
/>
</div>
</div>
);
}
}function mapStateToProps(state) {
// REDACTED
return {};
}function mapDispatchToProps(dispatch) {
return {
…bindActionCreators(actions, dispatch)
};
}
return connect(mapStateToProps, mapDispatchToProps)(BaseFilter);
}export default FilterWrapper;
Lets explain what’s going on here…
We’re creating a function that wraps a component and defines some behavior. This is shared behavior to all the wrapped components. In this example we only have the count label for the sake of example but you can obviously extend it to whatever you can.
Let’s take a look at the wrapped component (HotelClass) for this example
// src/components/Filters/HotelClass/index.jsimport React, { Component } from ‘react’;
import ReactDOM from ‘react-dom’;
import BaseFilterWrapper from ‘../BaseFilterWrapper’;class HotelClass extends Component {
render() {
return(
<div className=”hotelClassOptions”>
<ul>
<li className=”optionsdropdown”>
<label className=”optionTitle”>5</label>
<input onChange={ this.props.onCheckboxChange } type=”checkbox” value=”5" />
</li>
<li className=”optionsdropdown”>
<label className=”optionTitle”>4</label>
<input onChange={ this.props.onCheckboxChange } type=”checkbox” value=”4" />
</li>
</ul>
</div>
)
}
}let filterInfo = {
name: ‘hotel_class’,
class_name: ‘hotelClass’,
title: ‘Hotel Class’
};export default BaseFilterWrapper(HotelClass, filterInfo);
As you can see, when the checkbox changes, it’s calling this.props.onCheckboxChange which is coming from the higher order component and in turn will call the behavior there.
The final lines are the “magic” ones, I pass some `filterInfo` which you can use any way you see fit (or not), and I pass the `HotelClass` component wrapped in `BaseFilterWrapper` function.
Now, lets implement the logic to show the count label when we click the checkboxes and hide it if the count is 0
onCheckboxChange(e) {
let { count } = this.state;
var { checked, value } = e.target;
if (checked) {
count += 1;
} else {
count -= 1;
}
this.setState({
count
});
}
That’s about it.
Now, you can share similar component logic.
Benefits
The clear benefits of this approach is that if you have a share behavior for a set of components, you only connect the “base” to the store and you manage everything from it. The rest of your components are just “dumb components” and the get injected with the behavior.
The code is much cleaner and there’s a single point of entry for actions/state changes to the system, this simplifies debugging and testing a lot and overall makes the code more readable.