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.
Advertisement
Answer
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> ) }