appbaseio/reactivesearch

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

Summary

Maintainability
D
2 days
Test Coverage
import React, { Component } from 'react';

import {
    addComponent,
    removeComponent,
    watchComponent,
    updateQuery,
    setQueryListener,
} from '@appbaseio/reactivecore/lib/actions';
import {
    debounce,
    checkValueChange,
    checkPropChange,
    getClassName,
    checkSomePropChange,
} from '@appbaseio/reactivecore/lib/utils/helper';
import types from '@appbaseio/reactivecore/lib/utils/types';

import Input, { suggestionsContainer } from '../../styles/Input';
import InputIcon from '../../styles/InputIcon';
import CancelSvg from '../shared/CancelSvg';
import Title from '../../styles/Title';
import Container from '../../styles/Container';
import { connect } from '../../utils';

class TextField extends Component {
    constructor(props) {
        super(props);

        this.state = {
            currentValue: props.selectedValue || '',
        };
        this.locked = false;
        props.setQueryListener(props.componentId, props.onQueryChange, null);
    }

    componentWillMount() {
        this.props.addComponent(this.props.componentId);
        this.setReact(this.props);

        if (this.props.selectedValue) {
            this.setValue(this.props.selectedValue, true);
        } else if (this.props.defaultSelected) {
            this.setValue(this.props.defaultSelected, true);
        }
    }

    componentWillReceiveProps(nextProps) {
        checkPropChange(this.props.react, nextProps.react, () => {
            this.setReact(nextProps);
        });

        checkSomePropChange(this.props, nextProps, ['dataField', 'nestedField'], () => {
            this.updateQuery(this.state.currentValue, nextProps);
        });

        if (this.props.defaultSelected !== nextProps.defaultSelected) {
            this.setValue(nextProps.defaultSelected, true, nextProps);
        } else if (
            // since, selectedValue will be updated when currentValue changes,
            // we must only check for the changes introduced by
            // clear action from SelectedFilters component in which case,
            // the currentValue will never match the updated selectedValue
            this.props.selectedValue !== nextProps.selectedValue
            && this.state.currentValue !== nextProps.selectedValue
        ) {
            this.setValue(nextProps.selectedValue || '', true, nextProps);
        }
    }

    componentWillUnmount() {
        this.props.removeComponent(this.props.componentId);
    }

    setReact(props) {
        if (props.react) {
            props.watchComponent(props.componentId, props.react);
        }
    }

    static defaultQuery = (value, props) => {
        let query = null;
        if (value && value.trim() !== '') {
            query = {
                match: {
                    [props.dataField]: value,
                },
            };
        }
        if (query && props.nestedField) {
            return {
                query: {
                    nested: {
                        path: props.nestedField,
                        query,
                    },
                },
            };
        }
        return query;
    };

    handleTextChange = debounce((value) => {
        this.updateQuery(value, this.props);
    }, this.props.debounce);

    setValue = (value, isDefaultValue = false, props = this.props) => {
        // ignore state updates when component is locked
        if (props.beforeValueChange && this.locked) {
            return;
        }

        this.locked = true;
        const performUpdate = () => {
            this.setState(
                {
                    currentValue: value,
                },
                () => {
                    if (isDefaultValue) {
                        this.updateQuery(value, props);
                    } else {
                        // debounce for handling text while typing
                        this.handleTextChange(value);
                    }
                    this.locked = false;
                    if (props.onValueChange) props.onValueChange(value);
                },
            );
        };
        checkValueChange(props.componentId, value, props.beforeValueChange, performUpdate);
    };

    updateQuery = (value, props) => {
        const query = props.customQuery || TextField.defaultQuery;

        props.updateQuery({
            componentId: props.componentId,
            query: query(value, props),
            value,
            label: props.filterLabel,
            showFilter: props.showFilter,
            URLParams: props.URLParams,
        });
    };

    handleChange = (e) => {
        this.setValue(e.target.value);
    };

    clearValue = () => {
        this.setValue('', true);
    };

    renderCancelIcon = () => {
        if (this.props.showClear) {
            return this.props.clearIcon || <CancelSvg />;
        }
        return null;
    };

    renderIcons = () => (
        <div>
            {this.state.currentValue && this.props.showClear && (
                <InputIcon onClick={this.clearValue} iconPosition="right">
                    {this.renderCancelIcon()}
                </InputIcon>
            )}
        </div>
    );

    render() {
        return (
            <Container style={this.props.style} className={this.props.className}>
                {this.props.title && (
                    <Title className={getClassName(this.props.innerClass, 'title') || null}>
                        {this.props.title}
                    </Title>
                )}
                <div className={suggestionsContainer}>
                    <Input
                        type="text"
                        className={getClassName(this.props.innerClass, 'input') || null}
                        placeholder={this.props.placeholder}
                        onChange={this.handleChange}
                        value={this.state.currentValue}
                        onBlur={this.props.onBlur}
                        onFocus={this.props.onFocus}
                        onKeyPress={this.props.onKeyPress}
                        onKeyDown={this.props.onKeyDown}
                        onKeyUp={this.props.onKeyUp}
                        autoFocus={this.props.autoFocus}
                        innerRef={this.props.innerRef}
                        themePreset={this.props.themePreset}
                        showClear={this.props.showClear}
                    />
                    {this.renderIcons()}
                </div>
            </Container>
        );
    }
}

TextField.propTypes = {
    addComponent: types.funcRequired,
    removeComponent: types.funcRequired,
    setQueryListener: types.funcRequired,
    updateQuery: types.funcRequired,
    watchComponent: types.funcRequired,
    selectedValue: types.selectedValue,
    // component props
    autoFocus: types.bool,
    beforeValueChange: types.func,
    className: types.string,
    clearIcon: types.children,
    componentId: types.stringRequired,
    customQuery: types.func,
    dataField: types.stringRequired,
    debounce: types.number,
    defaultSelected: types.string,
    filterLabel: types.string,
    innerClass: types.style,
    innerRef: types.func,
    onBlur: types.func,
    onFocus: types.func,
    onKeyDown: types.func,
    onKeyPress: types.func,
    onKeyUp: types.func,
    onQueryChange: types.func,
    onValueChange: types.func,
    placeholder: types.string,
    react: types.react,
    ref: types.func,
    showClear: types.bool,
    showFilter: types.bool,
    style: types.style,
    themePreset: types.themePreset,
    title: types.title,
    URLParams: types.bool,
};

TextField.defaultProps = {
    className: null,
    debounce: 0,
    placeholder: 'Search',
    showClear: false,
    showFilter: true,
    style: {},
    URLParams: false,
};

const mapStateToProps = (state, props) => ({
    selectedValue:
        (state.selectedValues[props.componentId]
            && state.selectedValues[props.componentId].value)
        || null,
    themePreset: state.config.themePreset,
});

const mapDispatchtoProps = dispatch => ({
    addComponent: component => dispatch(addComponent(component)),
    removeComponent: component => dispatch(removeComponent(component)),
    updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
    watchComponent: (component, react) => dispatch(watchComponent(component, react)),
    setQueryListener: (component, onQueryChange, beforeQueryChange) =>
        dispatch(setQueryListener(component, onQueryChange, beforeQueryChange)),
});

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