appbaseio/reactivesearch

View on GitHub
packages/web/src/components/basic/URLParamsProvider.js

Summary

Maintainability
D
1 day
Test Coverage
import React, { Component } from 'react';
import { setHeaders, setValue } from '@appbaseio/reactivecore/lib/actions';
import types from '@appbaseio/reactivecore/lib/utils/types';
import { isEqual } from '@appbaseio/reactivecore/lib/utils/helper';

import Base from '../../styles/Base';
import { connect } from '../../utils';

const URLSearchParams = require('url-search-params');

class URLParamsProvider extends Component {
    componentDidMount() {
        const searchParams = this.props.getSearchParams
            ? this.props.getSearchParams()
            : window.location.search;
        this.params = new URLSearchParams(searchParams);
        this.currentSelectedState = this.props.selectedValues || {};
        window.onpopstate = () => {
            const activeComponents = Array.from(this.params.keys());

            // remove inactive components from selectedValues
            Object.keys(this.currentSelectedState)
                .filter(item => !activeComponents.includes(item))
                .forEach((component) => {
                    this.props.setValue(component, null);
                });

            // update active components in selectedValues
            Array.from(this.params.entries()).forEach((item) => {
                try {
                    this.props.setValue(item[0], JSON.parse(item[1]));
                } catch (e) {
                    // Do not set value if JSON parsing fails.
                }
            });
        };
    }

    componentWillReceiveProps(nextProps) {
        this.currentSelectedState = nextProps.selectedValues;
        if (!isEqual(this.props.selectedValues, nextProps.selectedValues)) {
            const searchParams = this.props.getSearchParams
              ? this.props.getSearchParams()
              : window.location.search;
            this.params = new URLSearchParams(searchParams);
            const currentComponents = Object.keys(nextProps.selectedValues);
            const urlComponents = Array.from(this.params.keys());

            currentComponents
                .filter(component => nextProps.selectedValues[component].URLParams)
                .forEach((component) => {
                    // prevents empty history pollution on initial load
                    if (
                        this.hasValidValue(this.props.selectedValues[component])
                        || this.hasValidValue(nextProps.selectedValues[component])
                    ) {
                        const selectedValues = nextProps.selectedValues[component];
                        if (selectedValues.URLParams) {
                            if (selectedValues.category) {
                                this.setURL(
                                    component,
                                    this.getValue({
                                        category: selectedValues.category,
                                        value: selectedValues.value,
                                    }),
                                );
                            } else {
                                this.setURL(component, this.getValue(selectedValues.value));
                            }
                        } else {
                            this.params.delete(component);
                            this.pushToHistory();
                        }
                    } else if (
                        !this.hasValidValue(nextProps.selectedValues[component])
                        && urlComponents.includes(component)
                    ) {
                        // doesn't have a valid value, but the url has a (stale) valid value set
                        this.params.delete(component);
                        this.pushToHistory();
                    }
                });

            // remove unmounted components
            Object.keys(this.props.selectedValues)
                .filter(component => !currentComponents.includes(component))
                .forEach((component) => {
                    this.params.delete(component);
                    this.pushToHistory();
                });

            if (!currentComponents.length) {
                Array.from(this.params.keys()).forEach((item) => {
                    this.params.delete(item);
                });
                this.pushToHistory();
            }
        }

        if (!isEqual(this.props.headers, nextProps.headers)) {
            nextProps.setHeaders(nextProps.headers);
        }
    }

    hasValidValue(component) {
        if (!component) return false;
        if (Array.isArray(component.value)) return !!component.value.length;
        return !!component.value;
    }

    getValue(value) {
        if (Array.isArray(value) && value.length) {
            return value.map(item => this.getValue(item));
        } else if (value && typeof value === 'object') {
            // TODO: support for NestedList
            if (value.location) return value;
            if (value.category) return value;
            return value.label || value.key || null;
        }
        return value;
    }

    setURL(component, value) {
        const searchParams = this.props.getSearchParams
            ? this.props.getSearchParams()
            : window.location.search;
        this.params = new URLSearchParams(searchParams);
        if (
            !value
            || (typeof value === 'string' && value.trim() === '')
            || (Array.isArray(value) && value.length === 0)
        ) {
            this.params.delete(component);
            this.pushToHistory();
        } else {
            const data = JSON.stringify(this.getValue(value));
            if (data !== this.params.get(component)) {
                this.params.set(component, data);
                this.pushToHistory();
            }
        }
    }

    pushToHistory() {
        const paramsSting = this.params.toString() ? `?${this.params.toString()}` : '';
        const base = window.location.href.split('?')[0];
        const newURL = `${base}${paramsSting}`;
        if (this.props.setSearchParams) {
            this.props.setSearchParams(newURL);
        } else if (window.history.pushState) {
            window.history.pushState({ path: newURL }, '', newURL);
        }
    }

    render() {
        return (
            <Base style={this.props.style} className={this.props.className}>
                {this.props.children}
            </Base>
        );
    }
}

URLParamsProvider.propTypes = {
    setHeaders: types.func,
    setValue: types.func,
    selectedValues: types.selectedValues,
    // component props
    children: types.children,
    headers: types.headers,
    style: types.style,
    className: types.string,
    getSearchParams: types.func,
    setSearchParams: types.func,
};

URLParamsProvider.defaultProps = {
    style: {},
    className: null,
};

const mapStateToProps = state => ({
    selectedValues: state.selectedValues,
});

const mapDispatchtoProps = dispatch => ({
    setHeaders: headers => dispatch(setHeaders(headers)),
    setValue: (component, value, label, showFilter, URLParams) =>
        dispatch(setValue(component, value, label, showFilter, URLParams)),
});

export default connect(
    mapStateToProps,
    mapDispatchtoProps,
)(URLParamsProvider);