By now everyone learned, use composition rather than inheritance. However, for some things and especially for developers new to the functional programming, it can be unclear how to use the composition properly.
One common problem is how to reuse the state and shared methods between components.
I’ll give an example with a List. The List definition is bellow, it has basic stuff such as nextItem()
, prevItem()
, onChange(selectedItem)
methods and selectedItem
in the state. It expects items
in the props
and of course a list is rendered on the end.
class List extends Component {constructor(props) {super(props);this.state = {selectedItem: 0,}}
componentWillUpdate({onChange, items}, nextState) {if (onChange && nextState.selectedItem !== this.state.selectedItem) {onChange(items[nextState.selectedItem]);}}
nextItem() {const {selectedItem} = this.state;const {items} = this.props;
**if** (items.length === **selectedItem** \+ 1) {
**return**;
}
**this**.setState({**selectedItem**: **selectedItem** \+ 1});
}
prevItem() {const {selectedItem} = this.state;
**if** (**selectedItem** \=== 0) {
**return**;
}
**this**.setState({**selectedItem**: **selectedItem** \- 1});
}
render() {const {items} = this.props;const {selectedItem} = this.state;
**return** (
<**ul**\>
{
items.map((item, index) => (
<**li className=**{**\`**${**selectedItem** \=== index ? **'selected'** : **''**}**\`**}>
{item}
</**li**\>
))
}
</**ul**\>
);
}}
Now when we have a nice and clean List here is a question.
Q: How to create ListVertical and ListHorizontal?
With a HOC (High Order Component), the withList
HOC. So basically, what we are going to do is to separate and pass down stuff we need to the wrapped component. Watch.
function withList(WrappedComponent) {return class extends Component {constructor(props) {super(props);this.state = {selectedIndex: 0,}}
componentWillUpdate({onChange, items}, nextState) {
**if** (onChange && nextState.**selectedIndex** !== **this**.**state**.**selectedIndex**) {
onChange(items\[nextState.**selectedIndex**\]);
}
}
_nextItem_ \= () => {
**const** {selectedIndex} = **this**.**state**;
**const** {items} = **this**.**props**;
**if** (items.**length** \=== selectedIndex + 1) {
**return**;
}
**this**.setState({**selectedIndex**: selectedIndex + 1});
};
_prevItem_ \= () => {
**const** {selectedIndex} = **this**.**state**;
**if** (selectedIndex === 0) {
**return**;
}
**this**.setState({**selectedIndex**: selectedIndex - 1});
};
_selectItem_ \= (selectedIndex) => {
**this**.setState({selectedIndex});
};
render() {
**const** {selectedIndex} = **this**.**state**;
**return** (
<**WrappedComponent** {...**this**.**props**}
**prevItem=**{**this**._prevItem_}
**nextItem=**{**this**._nextItem_}
**selectItem=**{**this**._selectItem_}
**selectedIndex=**{selectedIndex}
/>
);
}
}}
As you can see, the wrapped component will have all the methods and the selectedIndex
available. Instead using it from this
it is used from the props
. Much better!
It is important for the wrapped component to have ability to change used parent state. That way you get to keep the lifecycle and can create new methods for updating state. This means to fully reuse the state.
Notice
_selectItem_
is additionally implemented so that the wrapped component can change the_selectedIndex_
like it is a part of its state. That’s !important.
Lets check how ListHorizontal looks.
class ListHorizontal extends Component {customNextItem = () => {// Not really custom -_- const {selectedIndex, selectItem} = this.props;selectItem(selectedIndex + 1);}
render() {const {selectedIndex, prevItem, selectItem} = this.props;return (<div><img src="https://imgflip.com/s/meme/Troll-Face.jpg" alt="Horizontal list" /><div>Selected item index: {selectedIndex}</div><span onClick={prevItem}>Prev item</span><span onClick={this.customNextItem}>Next item</span></div>);}}
That’s it! Simple and clean. Everything is implemented in the withList
HOC and it can now be easily applied to any component. The most beautiful thing about HOC is that you can wrap as much as you like!
Homework: How does now the List looks?
Observations and in-advance comments:
ref
😭All read. Woohoo!