State change in parent component is not reflecting for child component in React

269 Views Asked by At

I'm using Sample tabs component in React as follows:

Sam

import React from 'react';
import ReactDOM from 'react-dom';
import Tabs from 'xxx/components/tabs';
import Tab from 'xxx/components/tab';
import TabHeader from 'xxx/components/tabheader';
import TabBody from 'xxx/components/tabbody';


class Sam extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            count: 0
        };
        this.chngState = this.chngState.bind(this);
    }
    chngState(ev){
        console.log("ev.target.value: ",ev.target.value);
        this.setState({count: parseInt(ev.target.value)});
    }
    render() {
        return (<div>
            <input type="number" onChange={(ev)=>{this.chngState(ev)}}/>
            <div className="reactComp">
                <Tabs>
                    <Tab active>
                        <TabHeader >Auto Attendant</TabHeader>
                        <TabBody className="pad-left-1r">
                            <h4>Testing Button {this.state.count}</h4>
                            <p>
                            This is just to demonstarate the use of tabs.
                            You could also make a `ul` inside like this:
                            </p>
                            <ul>
                                <li>First</li>
                                <li>Second</li>
                                <li>Third</li>
                                <li>Fourth</li>
                            </ul>
                        </TabBody>
                    </Tab>
                    <Tab>
                        <TabHeader>Call Park</TabHeader>
                        <TabBody className="pad-left-1r">
                            <h4>Testing Button 2</h4>
                            <p>
                            This is just to demonstarate the use of tabs.
                            You could also make a `ul` inside like this:
                            </p>
                            <ul>
                                <li>Fifth</li>
                                <li>Sixth</li>
                                <li>Seventh</li>
                                <li>Eighth</li>
                            </ul>
                        </TabBody>
                    </Tab>
                </Tabs>
            </div>
        </div>);
    }
}

ReactDOM.render(<Sam />, document.querySelector("#full_component"));

Which do render component as follows:

component

But when I'm making changes in input it is not reflecting in child (Which I think because I'm using React.cloneElement for coping the children)

So in my given scenario how can I fetch my requirement?

Where implementation of tabs component is as follows:

Tab

import React from 'react';
import PropTypes from 'prop-types';

/* eslint-disable react/prefer-stateless-function */
class Tab extends React.Component {

}

Tab.propTypes = {
    /**
     * Controls whether the current tab is active or inactive.
     * @ignore
     */
    active: PropTypes.bool,
    /**
     * Callback for activating tab
     */
    onActivation: PropTypes.func,
    /**
     * Makes the tab closable
     */
    closable: PropTypes.bool

};

Tab.defaultProps = {
    active: false,
    onActivation: null,
    closable: false
};

export default Tab;

TabBody

import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import tabStyles from './tab_body.css';

export default class TabBody extends React.Component {
    constructor (props) {
        super(props);
        this.state = {
            active: true
        };
    }

getTabBodyClass () {
    const {className} = this.props;
    let constructedClass = cx(tabStyles.tabBody, {
        [`${tabStyles.invisible}`]: !this.props.isActive
    }, className);
    return constructedClass;
}

render () {
    let tabClass = this.getTabBodyClass();
    return (<div className={tabClass} >
        {this.props.children}
    </div>);
}
}

TabBody.propTypes = {
    className: PropTypes.string,
    /**
     * children component
     */
    children: PropTypes.node,
    /**
     * Configuration holds the state of the tab
     * @ignore
     * @private
     */
    isActive: PropTypes.bool
};

TabBody.defaultProps = {
    className: '',
    children: null,
    isActive: false
};

TabHeader

import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import tabHeaderStyles from './tab_header.css';

export default class TabHeader extends React.Component {
    constructor (props) {
        super(props);
        this.state = {
            isActive: true
        };
        this.onHeaderClick = this.onHeaderClick.bind(this);
        this.onHeaderKeydown = this.onHeaderKeydown.bind(this);
        this.onTabClose = this.onTabClose.bind(this);
        this.onCloseButtonKeydown = this.onCloseButtonKeydown.bind(this);
    }

    onHeaderClick (e) {
        let handled = false;
        e.stopPropagation();
        if (typeof this.props.onActivationUser === 'function') {
            handled = this.props.onActivationUser(e.target);
        }
        if (handled) {
            e.preventDefault();
        } else {
            this.props.onActivation(e.target);
        }
    }

    onHeaderKeydown (e) {
        if (e.keyCode === 13 || e.keyCode === 32) {
            let handled = false;
            e.stopPropagation();
            if (typeof this.props.onActivationUser === 'function') {
                handled = this.props.onActivationUser(e.target);
            }
            if (handled) {
                e.preventDefault();
            } else {
                this.props.onActivation(e.target);
            }
        }
    }

    onTabClose (e) {
        e.stopPropagation();
        let handled = false;
        if (typeof this.props.onCloseUser === 'function') {
            handled = this.props.onCloseUser(e.target);
        }
        if (handled) {
            e.preventDefault();
        } else {
            this.props.onClose(e.target);
        }
    }

    onCloseButtonKeydown (e) {
        e.stopPropagation();
        if (e.keyCode === 13) {
            let handled = false;
            e.stopPropagation();
            if (typeof this.props.onCloseUser === 'function') {
                handled = this.props.onCloseUser(e.target);
            }
            if (handled) {
                e.preventDefault();
            } else {
                this.props.onClose(e.target);
            }
        }
    }

    getTabHeaderClass (receivedClass) {
        const {isActive, size, type, align, orientation} = this.props;
        let className = cx(`${tabHeaderStyles.tabHeader} ft-sz-14`, {
            [`${tabHeaderStyles.verticalOrientation}`]: orientation === 'vertical',
            [`${tabHeaderStyles.tabsHeader}`]: type === 'tabs',
            [`${tabHeaderStyles.pillsHeader}`]: type === 'pills' && orientation !== 'vertical',
            [`${tabHeaderStyles.pillsHeaderVertical}`]: type === 'pills' && orientation === 'vertical',
            [`${tabHeaderStyles.grayHeader}`]: type === 'gray' && orientation !== 'vertical',
            [`${tabHeaderStyles.grayHeaderVertical}`]: type === 'gray' && orientation === 'vertical',
            [`${tabHeaderStyles.headerActiveTabs}`]: type === 'tabs' && isActive,
            [`${tabHeaderStyles.headerActivePills}`]: type === 'pills' && isActive,
            [`${tabHeaderStyles.headerActiveGray}`]: type === 'gray' && isActive,
            [`${tabHeaderStyles.smallHeader}`]: size === 'small',
            [`${tabHeaderStyles.largeHeader}`]: size === 'large',
            [`${tabHeaderStyles.alignJustified}`]: align === 'justified'
        });
        return cx(className, receivedClass);
    }

    render () {
        const { className, closable, size, tabIndex, onActivation, onClose, onCloseUser,
            onActivationUser, align, isActive, uuid, ...otherProps } = this.props;
        let tabClass = this.getTabHeaderClass(className);
        return (
            <button
                className={tabClass}
                onMouseDown={this.onHeaderClick}
                onKeyDown={this.onHeaderKeydown}
                data-name={this.props.uuid}
                {...otherProps}
            >
                {this.props.children}
                {closable &&
                <a
                    className={tabHeaderStyles.closeContainer}
                    data-name={this.props.uuid}
                    onMouseDown={this.onTabClose}
                    onKeyDown={this.onCloseButtonKeydown}
                    href="javascript:;"
                >
                    &#10005;
                </a>
                }
            </button>
        );
    }
}

TabHeader.propTypes = {
    /**
     * Alignment of the titles
     * @ignore
     */
    align: PropTypes.oneOf(['start', 'justified']),
    /**
     * HTML Markup to be placed inside the header
     * @ignore
     */
    children: PropTypes.node,
    /**
     * Custom class for the Header
     */
    className: PropTypes.string,
    /**
     * If tab is closable
     */
    closable: PropTypes.bool,
    /**
     * State of active tab
     * @ignore
     * @private
     */
    isActive: PropTypes.bool,
    /**
     * On Activation callback from tabs
     * @ignore
     */
    onActivation: PropTypes.func,
    /**
     * On Activation callback from tab
     * @ignore
     */
    onActivationUser: PropTypes.func,
    /**
     * On Close callback from tabs
     * @ignore
     */
    onClose: PropTypes.func,
    /**
     * On Close callback from tab
     * @ignore
     */
    onCloseUser: PropTypes.func,
    /**
     * Size of the tabs component
     * @ignore
     * @private
     */
    size: PropTypes.oneOf(['small', 'regular', 'large']),
    /**
     * Orientaion of the menu items
     * @ignore
     * @private
     */
    orientation: PropTypes.oneOf(['vertical', 'horizontal']),
    /**
     * Index of the tab
     * @ignore
     * @private
     */
    tabIndex: PropTypes.number,
    /**
     * Type of the header
     * @ignore
     */
    type: PropTypes.oneOf(['tabs', 'pills', 'gray']),
    /**
     * Unique identifier for tab header
     * @ignore
     */
    uuid: PropTypes.string

};

TabHeader.defaultProps = {
    align: 'start',
    children: null,
    className: '',
    closable: false,
    orientation: 'horizontal',
    isActive: false,
    onActivation: null,
    onActivationUser: null,
    onClose: null,
    onCloseUser: null,
    size: 'regular',
    tabIndex: 0,
    type: 'tabs',
    uuid: null
};

Tabs

import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Helpers from '../../utils/helpers';
import Tab from '../tab';
import TabHeader from '../tabheader';
import TabBody from '../tabbody';
import Toast from '../toast';
import tabsStyles from './tabs.css';

/**
 * Tabs are a great way to allow the user to switch between
 * several pages that are full screen.
 */
export default class Tabs extends React.Component {
    constructor (props) {
        super(props);
        this.state = {
            currentTab: 0,
            compUpdateCalled:0,
            compMountCalled:0,
        };
        this.titleList = [];
        this.bodyList = [];
        this.curentTabUUID = null;
        this.setActiveTab = this.setActiveTab.bind(this);
        this.tabClickHandler = this.tabClickHandler.bind(this);
        this.tabCloseHandler = this.tabCloseHandler.bind(this);
        this.updateChildElm = this.updateChildElm.bind(this);
        this.activateTab = this.activateTab.bind(this);
    }

    updateChildElm () {
        const {align, children, orientation, type, size} = this.props;
        let uuid = 0;
        if (children && children.length > 0) {
            children.forEach((child, idx) => {
                if (child.type === Tab) {
                    uuid = Helpers.getComponentId('Tab');
                    if (idx === 0 || child.props.active === true) {
                        this.curentTabUUID = uuid;
                    }
                    let grandChildren = child.props.children;
                    grandChildren.forEach((grandChild) => {
                        if (grandChild.type === TabHeader) {
                            grandChild = React.cloneElement(grandChild, {
                                closable: child.props.closable,
                                isActive: uuid === this.curentTabUUID,
                                key: idx.toString(),
                                onActivation: this.tabClickHandler,
                                onActivationUser: child.props.onActivation,
                                onClose: this.tabCloseHandler,
                                onCloseUser: child.props.onClose,
                                orientation: orientation,
                                align: align,
                                type: type,
                                size: size,
                                uuid: uuid
                            });
                            this.titleList.push(grandChild);
                        }
                        if (grandChild.type === TabBody) {
                            grandChild = React.cloneElement(grandChild, {
                                uuid: uuid,
                                isActive: uuid === this.curentTabUUID,
                                key: idx.toString()
                            });
                            this.bodyList.push(grandChild);
                        }
                    });
                }
            });
        }
        this.setActiveTab(this.curentTabUUID);
    }

    componentWillMount () {
        console.log("compMountCalled: ",this.state.compUpdateCalled);
        this.updateChildElm();
    }
    componentWillReceiveProps () {
        console.log("compUpdateCalled: ",this.state.compUpdateCalled);
        this.updateChildElm();
    }

    setActiveTab (uuid) {
        this.setState({
            currentTab: uuid
        });
    }

    getTabsClass (recievedClass) {
        const {orientation} = this.props;
        let augmentedClass = cx(tabsStyles.tabsContainer, {
            [`${tabsStyles.verticalOrientation}`]: orientation === 'vertical'
        });
        return cx(augmentedClass, recievedClass);
    }

    getTabHeaderClass () {
        const orientation = this.props.orientation;
        let augmentedClass = cx(tabsStyles.tabHeaderContainer, {
            [`${tabsStyles.verticalOrientationHeader} flex-col`]: orientation === 'vertical'
        });
        return augmentedClass;
    }

    getTabBodyClass () {
        const orientation = this.props.orientation;
        let augmentedClass = cx(tabsStyles.tabBodyContainer, 'flex1', {
            [`${tabsStyles.verticalOrientationBody}`]: orientation === 'vertical'
        });
        return augmentedClass;
    }

    activateTab (list) {
        return list.map((item) => (
            React.cloneElement(item, {
                isActive: item.props.uuid === this.state.currentTab
            }))
        );
    }

    tabClickHandler (e) {
        if (typeof this.props.onTabChange === 'function') {
            this.props.onTabChange(this, e.target);
        }
        this.setActiveTab(e.getAttribute('data-name'));
    }

    tabCloseHandler (e) {
        if (typeof this.props.onTabChange === 'function') {
            this.props.onTabChange(this, e.target);
        }
        if (this.titleList.length === 1) {
            const toastWithTitle = (
                <Toast
            key={Date.now()}
            type="danger"
                >
                Cannot close the last remaining tab of the tabset.
            </Toast>);
            window.collab.notify(toastWithTitle);
        } else {
            let closeTabIndex = this.titleList.findIndex((item) => (item.props.uuid === e.getAttribute('data-name')));
            if (this.titleList[closeTabIndex].props.uuid === this.curentTabUUID) {
                let nextSelect = this.titleList[closeTabIndex - 1];
                if (!nextSelect) {
                    nextSelect = this.titleList[closeTabIndex + 1];
                }
                this.curentTabUUID = nextSelect.props.uuid;
            }
            this.titleList.splice(closeTabIndex, 1);
            this.bodyList.splice(closeTabIndex, 1);
        }
        this.setActiveTab(this.curentTabUUID);
    }

    render () {
        const {align, children, className, onTabChange,
            orientation, type, size, ...otherProps} = this.props;
        let tabsClass = this.getTabsClass(className);
        let tabHeaderClass = this.getTabHeaderClass();
        let tabBodyClass = this.getTabBodyClass();

        return (
            <div className={tabsClass} {...otherProps}>
    <div className={tabHeaderClass}>
            {this.activateTab(this.titleList)}
    </div>
        <div className={tabBodyClass}>
            {this.activateTab(this.bodyList)}
    </div>
        </div>
    );
    }
}

Tabs.propTypes = {
    /**
     * Alignment od the header
     */
    align: PropTypes.oneOf(['start', 'justified']),
    /**
     * HTML Markup to be placed inside the header
     * @ignore
     */
    children: PropTypes.node,
    /**
     * Custom class for the Header
     */
    className: PropTypes.string,
    /**
     * Orientation configuration
     */
    orientation: PropTypes.oneOf(['vertical', 'horizontal']),
    /**
     * Callback to be called whenever a tab-change takes place
     * with currentTab as first parameter and nextTab as the second parameter.
     */
    onTabChange: PropTypes.func,
    /**
     * Size of the tabs component
     */
    size: PropTypes.oneOf(['small', 'regular', 'large']),
    /**
     * Type of the header
     */
    type: PropTypes.oneOf(['tabs', 'pills', 'gray'])
};

Tabs.defaultProps = {
    align: 'start',
    className: '',
    children: null,
    orientation: 'horizontal',
    onTabChange: null,
    size: 'regular',
    type: 'tabs'
};
0

There are 0 best solutions below