paint-brush
React — Reusable component stateby@lukabracanovic
3,965 reads
3,965 reads

React — Reusable component state

by Luka BracanovicOctober 21st, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

By now everyone learned, use composition rather than inheritance. However, for some things and especially for developers new to the functional <a href="https://hackernoon.com/tagged/programming" target="_blank">programming</a>, it can be unclear how to use the composition properly.

Company Mentioned

Mention Thumbnail
featured image - React — Reusable component state
Luka Bracanovic HackerNoon profile picture

How to share the state and methods between components.

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 itemsin 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 withListHOC 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:

  • I find approach with HOC much more flexible than using Container 👎
  • I know horizontal and vertical list can be done with CSS 😒
  • Code above hasn’t been run 😱
  • Don’t make me write an example with extending the List 😝
  • Downside is working with the ref 😭

All read. Woohoo!