randytarampi/me

View on GitHub
packages/jsx/src/lib/components/posts.jsx

Summary

Maintainability
A
30 mins
Test Coverage
A
100%
import {logger} from "@randy.tarampi/browser-logger";
import Dimensions from "@randy.tarampi/react-dimensions";
import SchemaJsonLdComponent from "@randy.tarampi/schema-dot-org-json-ld-components";
import {ItemList as SchemaItemList, ListItem as SchemaListItem} from "@randy.tarampi/schema-dot-org-types";
import {List} from "immutable";
import PropTypes from "prop-types";
import React, {PureComponent} from "react";
import Infinite from "react-infinite";
import LoadingSpinner from "../components/loadingSpinner";
import {ConnectedErrorWrapper} from "../containers";
import computePostHeight from "../util/computePostHeight";
import getComponentForType from "../util/getComponentForType";
import {
    ErrorENOCONTENTContentComponent,
    ErrorESERVERContentComponent,
    mapErrorCodeToErrorContentComponent as defaultMapErrorCodeToErrorContent
} from "./error";
import PostComponent from "./post";

export const mapPostsErrorCodeToErrorContentComponent = errorCode => {
    switch (errorCode) {
        case "EFETCH":
        case "ESERVER":
            return ErrorESERVERContentComponent;

        case "ENOPOSTS":
            return ErrorENOCONTENTContentComponent;

        default:
            return defaultMapErrorCodeToErrorContent(errorCode);
    }
};

export class PostsComponent extends PureComponent {
    constructor(props) {
        super(props);

        this.state = {};
        this.state.elementHeight = this.calculateElementHeight(this.state, props);
    }

    componentDidMount() {
        if (this.props.shouldFetchPostsOnMount) {
            this.props.fetchPosts();
        }
    }

    calculateElementHeight({elementHeight: elementHeightState}, props) {
        const {posts, postsLimit, containerWidth} = props;

        let postsArray = posts && posts.toArray();

        if (Number.isFinite(postsLimit)) {
            postsArray = postsArray.slice(0, postsLimit);
        }

        return postsArray
            ? postsArray.map((post, index) => {
                const cachedPostHeight = elementHeightState && elementHeightState[index];

                return computePostHeight(containerWidth)(post, cachedPostHeight);
            })
            : [window.innerHeight];
    }

    componentDidUpdate(previousProps) {
        this.setState((state, props) => {
            if (
                previousProps.containerWidth !== props.containerWidth
                || previousProps.posts !== props.posts
            ) {
                return {
                    elementHeight: this.calculateElementHeight(state, props)
                };
            }

            return state;
        });
    }

    render() {
        const {posts, containerHeight, containerWidth, fetchPosts, isLoading, postsLimit, ...props} = this.props;

        let postsArray = posts && posts.toArray();

        if (Number.isFinite(postsLimit)) {
            postsArray = postsArray.slice(0, postsLimit);
        }

        const itemList = postsArray
            ? new SchemaItemList({
                numberOfItems: postsArray.length,
                itemListOrder: "Descending",
                itemListElement: postsArray.map((post, index) => new SchemaListItem({
                    item: post.toSchema(),
                    position: index + 1,
                    url: `${window.location.origin}${window.location.pathname}#${post.uid}`
                }))
            })
            : [];

        return <ConnectedErrorWrapper
            key="posts-error-wrapper"
            mapErrorCodeToErrorContentComponent={mapPostsErrorCodeToErrorContentComponent}
        >
            <SchemaJsonLdComponent markup={itemList}/>
            <Infinite
                useWindowAsScrollContainer={true}
                elementHeight={
                    postsArray && postsArray.length === this.state.elementHeight.length
                        ? this.state.elementHeight
                        : this.calculateElementHeight(this.state, this.props)
                }
                infiniteLoadBeginEdgeOffset={window.innerHeight}
                preloadBatchSize={Infinite.containerHeightScaleFactor(1 / 8)}
                preloadAdditionalHeight={Infinite.containerHeightScaleFactor(8)}
                onInfiniteLoad={fetchPosts}
                isInfiniteLoading={isLoading}
                loadingSpinnerDelegate={<LoadingSpinner/>}
                {...props}
            >
                {
                    postsArray
                        ? postsArray.map(post => {
                            let Constructor;

                            try {
                                Constructor = getComponentForType(post.type);
                            } catch (error) {
                                logger.warn(error, `Can't \`getComponentForType\` for \`${post.type}\`, just using \`Post\` instead\``);
                                Constructor = PostComponent;
                            }

                            return <Constructor
                                key={post.uid}
                                post={post}
                                containerHeight={containerHeight}
                                containerWidth={containerWidth}
                            />;
                        })
                        : <div/>
                }
            </Infinite>
        </ConnectedErrorWrapper>;
    }
}

PostsComponent.propTypes = {
    containerHeight: PropTypes.number,
    containerWidth: PropTypes.number,
    postsLimit: PropTypes.number,
    fetchPosts: PropTypes.func.isRequired,
    isLoading: PropTypes.bool,
    shouldFetchPostsOnMount: PropTypes.bool.isRequired,
    posts: PropTypes.instanceOf(List)
};

PostsComponent.defaultProps = {
    isLoading: false,
    shouldFetchPostsOnMount: false,
    postsLimit: Infinity
};

export const DimensionsWrappedPosts = Dimensions()(PostsComponent);

export const DimensionsContainerWrappedPosts = props => <div className="dimensions-container--posts">
    <DimensionsWrappedPosts {...props}/>
</div>;

export default DimensionsContainerWrappedPosts;