Skip to content

Commit

Permalink
feat #299 - Refac Tabs, fix flicker when switching
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-dassana committed Apr 19, 2021
1 parent cdf65db commit 4d9d586
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 19 deletions.
43 changes: 43 additions & 0 deletions src/components/Tabs/TabPane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { motion } from 'framer-motion'
import { styleguide } from 'components/assets/styles'
import { TabConfig } from '.'
import React, { FC } from 'react'

const { font, spacing } = styleguide

const useStyles = createUseStyles({
tabPane: {
...font.body,
display: ({ isActive }) => (isActive ? 'block' : 'none'),
padding: `${spacing.m}px ${spacing.l}px`
}
})

interface TabPaneProps {
isActive: boolean
tabConfigItem: TabConfig
}

const TabPane: FC<TabPaneProps> = ({
isActive,
tabConfigItem: { classes = [], render }
}: TabPaneProps) => {
const compClasses = useStyles({ isActive })

return (
<motion.section
animate={isActive ? 'active' : 'inactive'}
transition={{ duration: 0.5 }}
variants={{
active: { opacity: 1 },
inactive: { opacity: 0 }
}}
>
<div className={cn(compClasses.tabPane, classes)}>{render()}</div>
</motion.section>
)
}

export default TabPane
30 changes: 11 additions & 19 deletions src/components/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { generateThemedTabsListStyles } from './utils'
import Tab from './Tab'
import TabPane from './TabPane'
import React, { FC, ReactNode, useState } from 'react'
import { styleguide, ThemeType } from 'components/assets/styles'

Expand Down Expand Up @@ -58,29 +59,13 @@ export const Tabs: FC<TabsProps> = ({
const [activeIndex, setActiveIndex] = useState(
tabConfig[defaultActiveIndex] ? defaultActiveIndex : 0
)
const [tabSwitching, setTabSwitching] = useState(false)

const tabsClasses = useStyles()

const tabPaneClasses = cn(
tabsClasses.tabPane,
tabConfig[activeIndex].classes
)

const onClickTab = (tabIndex: number) => {
if (onTabChange) onTabChange(tabConfig[tabIndex])

setTabSwitching(true)
setActiveIndex(tabIndex)

/**
* Toggling the state of tabSwitching ensures a full unmount of the previously rendered component.
* If two tabs render the same component but with different props, React does not unmount the component
* which leads to unexpected behavior and potentially stale state for the newly rendered component.
* This can be also be solved by adding a "key" property to the tab panes rendered with
* tabConfig[activeIndex].render() but because there is no way to enforce this, this is a fallback.
*/
setTimeout(() => setTabSwitching(false))
}

const renderTabItems = () =>
Expand All @@ -94,16 +79,23 @@ export const Tabs: FC<TabsProps> = ({
/>
))

const renderTabPanes = () =>
tabConfig.map((tabConfigItem, i) => (
<TabPane
isActive={i === activeIndex}
key={i}
tabConfigItem={tabConfigItem}
/>
))

if (!tabConfig.length) {
throw new Error('Tab config should have at least one item in the array')
}

return (
<div className={cn(classes)}>
<div className={tabsClasses.tabsList}>{renderTabItems()}</div>
<div className={tabPaneClasses}>
{!tabSwitching && tabConfig[activeIndex].render()}
</div>
{renderTabPanes()}
</div>
)
}

0 comments on commit 4d9d586

Please sign in to comment.