2

I am trying to hide/show components in React based on some state. The main issue I am facing is to maintain the internal state of the components during hiding and showing. The below is the code that does what I expect and maintains the state of each of the components (Africa, Europe, America, Asia):

render() {
    const {selectedTab} = this.state;
    return (
        <div>
            <div className={selectedTab === 'africa' ? '' : 'hidden-tab'}><Africa /></div>
            <div className={selectedTab === 'europe' ? '' : 'hidden-tab'}><Europe /></div>
            <div className={selectedTab === 'america' ? '' : 'hidden-tab'}><America /></div>
            <div className={selectedTab === 'asia' ? '' : 'hidden-tab'}><Asia /></div>
        </div>
    )
}


//regions.scss
.hidden-tab {
   display: none
}

However, I am not satisfied with the cleanliness of the above code and would like to refactor, which is where I am facing issues. This is what I have done:

render() {
    const {selectedTab} = this.state;
    const tabToRegionMap = {
        'africa': <Africa />,
        'eruope': <Europe />,
        'america': <America />,
        'asia': <Asia />
    };
    const {[selectedTab]: selectedRegion, ...regionsToHide} = tabToRegionMap;

    return (
        <div>
            <div className={'hidden-tab'}>
                {Object.values(regionsToHide)}
            </div>
            {selectedRegion}
        </div>
    );

The above try does show/hide the copmonents but does not maintain the internal state of the components during hiding/showing - it seems like they are being unmounted and remounted every time.

Could anyone please help me solve the problem or suggest a better way of doing it? That would be much appreciated.

PS I would prefer not to move the state to the parent or Redux as there is a lot of boilerplate involved and the states of the individual components are very complex.

craig-nerd
  • 718
  • 1
  • 12
  • 32

1 Answers1

0

If I understand your question you are essentially looking for a way to clean up the code and keep the children components mounted.

The issue with the proposed solution is that each time it is rendered and supposed to hide tabs is it recreating the elements. They are swapping between being rendered into the <div className={'hidden-tab'}> and not, remounting each time when selected tab updates.

I suggest just abstracting the div elements into a new component that conditionally applies the 'hidden-tab' classname.

const Tab = ({ children, selectedTab, tab }) => (
  <div className={selectedTab === tab ? '' : 'hidden-tab'}>
    {children}
  </div>
);

...

render() {
  const {selectedTab} = this.state;
  return (
    <div>
      <Tab selectedTab={selectedTab} tab='africa'><Africa /></Tab>
      <Tab selectedTab={selectedTab} tab='europe'><Europe /></Tab>
      <Tab selectedTab={selectedTab} tab='america'><America /></Tab>
      <Tab selectedTab={selectedTab} tab='asia'><Asia /></Tab>
    </div>
  )
}

If you wanted to take it a step further, you could also abstract the wrapping div of these tabs into a container component that stored the selected tab and provided the value to children tabs via the Context API so a selectedTab wouldn't need to be explicitly passed to each.

Example:

import { createContext, useContext } from "react";

const TabContext = createContext({
  selectedTab: null
});

const useSelectedTab = () => useContext(TabContext);

const Tabs = ({ children, selectedTab }) => (
  <TabContext.Provider value={{ selectedTab }}>{children}</TabContext.Provider>
);

const Tab = ({ children, tab }) => {
  const { selectedTab } = useSelectedTab();
  return (
    <div className={selectedTab === tab ? "" : "hidden-tab"}>{children}</div>
  );
};

Usage:

render() {
  const {selectedTab} = this.state;
  return (
    <Tabs selectedTab={selectedTab}>
      <Tab tab='africa'><Africa /></Tab>
      <Tab tab='europe'><Europe /></Tab>
      <Tab tab='america'><America /></Tab>
      <Tab tab='asia'><Asia /></Tab>
    </div>
  )
}

Edit showing-hiding-react-components-does-not-maintain-internal-state

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks for your suggestion @DrewReese! I tried using the first solution which does show/hide as expected. However, the internal state is still not maintained. Any idea into why this is happening? – craig-nerd Nov 22 '21 at 05:44
  • @John Do you have an example of one of these country components and the sort of state you are wanting them to maintain? If they remain mounted then I don't see why state wouldn't be persisted even while hidden. I'll test in my linked CSB. – Drew Reese Nov 22 '21 at 05:47
  • It's difficult to paste it here as they are quite complex components which make use of components from our internal React framework as the main components. Perhaps it's something to do with those components I'm pulling from the framework. – craig-nerd Nov 22 '21 at 05:51
  • I tried putting the new component both in a class method and render(). Is this where I am going wrong? Something like this: ``` buildTabContent() { const Tab = ({ children, selectedTab, tab }) => (
    {children}
    ); } render() { return this.buildTabContent(); } ```
    – craig-nerd Nov 22 '21 at 05:52
  • @John Here's a [fork](https://codesandbox.io/s/showing-hiding-react-components-does-not-maintain-internal-state-forked-r8hw8?file=/src/App.js) of my CSB where I added some basic state to each `Country` component, independently updated by each one rendered, and even applied `display: none;` to the hidden ones and they maintain their internal state. Granted, the CSB is the second implementation I suggested, but it really only abstracts away the active tab value for you. It's difficult to say why your components are not staying mounted without seeing your updated version. – Drew Reese Nov 22 '21 at 05:56
  • @John Ah, yes, is `Tab` a *new* component each time `buildTabContent` is invoked? That could be doing it. – Drew Reese Nov 22 '21 at 05:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/239442/discussion-between-john-and-drew-reese). – craig-nerd Nov 22 '21 at 07:06