BaseComponent for react

tetragius

Tetragius

Posted on June 14, 2019

BaseComponent for react

In most of my projects, I use inheritance from other components. And my base component is BaseComponent.

I know about the article "Composition vs inheritance", here only my point of view and some consequences from life experience.

My BaseComponent file look like this

import * as React from 'react';
import { Subject } from 'rxjs';

export interface IBaseComponentProps {
    hidden?: boolean;
}

export interface IBaseComponent<T> {
    _error?: boolean;
    view(template?: T): JSX.Element;
    errorView(): JSX.Element;
}


export default class BaseComponent<P = any, S = any, V = any>
    extends React.Component<Readonly<P> & IBaseComponentProps, S>
    implements React.Component<Readonly<P> & IBaseComponentProps, S>, IBaseComponent<V>{

    _error: boolean = false;

    constructor(props: P) {
        super(props);
    }

    componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
        this._error = true;
    }

    view(template?: V): JSX.Element {
        return null;
    }

    errorView() {
        return null;
    }

    render() {
        if (this.state && this._error) {
            return this.errorView();
        }

        const props = this.props;
        if (!props.hidden) {
            return this.view();
        }
        else {
            return null;
        }
    }
}

Further, when creating a component instead of,

class MyComponent extends React.Component<IProps, IState>

I do (ITemplate - optional)

class MyComponent extends BaseComponent<IProps, IState, ITemplate>

And what do I get from this?

And from the use of inheritance in general?

Consider the example of a component of the list item

interface ISimpleListItemProps<T> extends IBaseComponentProps {
    item: IItem & T;
    onClick?: (item: IItem & T) => void;
}

class SimpleListItem<P, S, T> extends BaseComponent<ISimpleListItemProps<T> & P>{
    constructor(props) {
        super(props);
    }

    onClick(item: IItem & T) {
        this.props.onClick(item);
    }

    view(props) {
        const item = props.item;
        return (
            <div 
            className="simple-list-item" 
            onClick={() => this.onClick(item)}>{item.title}
            </div>
        );
    }
}

Firstly, all components inherited from BaseComponent appear the hidden property
And if we need to hide a component we can use

    <SimpleListItem hidden={expression} ... />

instead of

    { expression && <SimpleListItem ... />}

I can also get the props in the view without using this and determine the errorView to display in case of a component crash.


In my case, BaseComponent also has a stream property.

    ...
    _error: boolean = false;
    _stream: Subject<any>;

    get stream(): Subject<any> { return this._stream }
    set stream(val: Subject<any>) { this._stream.next(val) }

    constructor(props: P) {
        super(props);
        this._stream = this.props.stream; /*!!!*/
    }
    ...

That allows me to add a common messaging bus for all components in one parent, which is convenient in some cases

For this, there is also the BaseContainer component, in which you can enable thread creation in the constructor. The presence of such a container allows you to better control and maintain the code.

    ...
    private _stream;

    get stream() { return this._stream };
    set stream(data) { this._stream.next(data) };

    constructor(props: P & IBaseContainerProps<T>, createStream?: boolean) {
        super(props);
        if (createStream) {
            this._stream = new Subject();
        }
    }
    ...

Return to inheritance and list items.

Now if I want to extend the functionality of the list item, I can inherit from the existing

interface IExtendedListItemProps<T> {
    onSelect?: (item: IExtendedItem & T) => void;
    onRemove?: (item: IExtendedItem & T) => void;
}

interface IExtendedListItemState {
    selected?: boolean;
}

export default class ExtendedListItem<P, S, T> extends SimpleListItem<IExtendedListItemProps<T> & P, IExtendedListItemState | S, IExtendedItem & T>{

    constructor(props) {
        super(props);
    }

    state = { selected: this.props.item.selected || false }

    onSelect(item: IExtendedItem & T) {
        this.setState({ selected: !this.state.selected })
        this.props.onSelect && this.props.onSelect(item);
    }

    onRemove(item: IExtendedItem & T) {
        this.props.onRemove && this.props.onRemove(item);
    }

    view() {

        const props = this.props;
        const state = this.state;
        const item = props.item;

        return (
            <div className={`extended-list-item${state.selected && ' extended-list-item__selected' || ''}`}>
                <div className="extended-list-item_select" onClick={() => this.onSelect(item)}></div>
                <>
                    {super.view()}
                </>
                <div className="extended-list-item_remove" onClick={() => this.onRemove(item)}></div>
            </div>
        );
    }
}

The new component has both the OnClick method and two new methods. I also use super.view () in a wrapper.

Create a drop-down list item that still has delete and highlight methods, but overrides the OnClick event — it now expands the item

...
    onClick(item: IExpandableItem & T) {
        this.toggle()
        this.stream.next(item);
    }

    view(template?: { body?: JSX.Element, footer?: JSX.Element }) {

        const props = this.props;
        const state = this.state;
        const item = props.item;

        const footer = template && template.footer || <div className="simple-footer">[---]</div>
        const body = template && template.body || item.body;

        return (
            <div className={`expandable-list-item${state.selected && ' expandable-list-item__selected' || ''}`}>
                <div className={"expandable-list-item_head"}>
                    {super.view()}
                </div>
                {
                    state.expanded && <div className="expandable-list-item_body">{body}</div>
                }
                {footer}
            </div>
        );
    }
...

Now I use the template attribute functions view and allows me to very briefly create a new component with a new footer

export default class ExtendedExpandableListItem
    extends ExpandableListItem<IExtendedExpandableListItemProps, IExtendedExpandableListItemState, IExtendedExpandableItem>{
    view() {
        const item = this.props.item;
        const footer = <div className="special-footer">{item.footer}</div>;
        return super.view({ footer });
    }
}

Conclusion

In some cases, inheritance provides an obvious advantage in the code before HOC or the composition of components. The readability of the code increases.
I also noticed that the use of inheritance can reduce the size of the final bundle of the application, since inheritance turns the webpack into prototype inheritance, and the HOC and composition of the components turn into new closure functions

Also, the use of inheritance from generic components like BaseComponent allows you to keep common logic in one place and, if necessary, redefine its behavior.

Thanks!

my previous article

💖 💪 🙅 🚩
tetragius
Tetragius

Posted on June 14, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related