React Dynamic Tabs with full API
Support react >=
v16.8.0
- Based on React hook
- Open & Close & Select & Refresh
- lazy/eager loading
- Customizable style
- Full API
- Return to last used tab when closing selected tab
- PanelList can be rendered outside the TabList container
- ARIA accessible
- Supporting custom Tab component
- Batching updates
- Installation
- Basic Example
- Simple manipulation
- Options
- Instance methods
- tabData
- Lazy Loading
- Styling
- Caveats
- Deprecated features
- Test
- License
$ npm install react-dyn-tabs --save
or
$ yarn add react-dyn-tabs
import React from 'react';
import 'react-dyn-tabs/style/react-dyn-tabs.css';
import 'react-dyn-tabs/themes/react-dyn-tabs-card.css';
import useDynTabs from 'react-dyn-tabs';
export default () => {
const options = {
tabs: [
{
id: '1',
title: 'tab 1',
panelComponent: (porps) => <p> panel 1 </p>,
},
{
id: '2',
title: 'tab 2',
panelComponent: (porps) => <p> panel 2 </p>,
},
],
selectedTabID: '1',
};
const [TabList, PanelList] = useDynTabs(options);
return (
<>
<TabList></TabList>
<PanelList></PanelList>
</>
);
};
import React from 'react';
import 'react-dyn-tabs/style/scss/react-dyn-tabs.scss';
import 'react-dyn-tabs/themes/scss/react-dyn-tabs-card.scss';
import useDynTabs from 'react-dyn-tabs';
export default () => {
const options = {
tabs: [
{
id: '1',
title: 'tab1',
panelComponent: (porps) => <p> panel 1 </p>,
},
{
id: '2',
title: 'tab2',
panelComponent: (porps) => <p> panel 2 </p>,
},
],
selectedTabID: '1',
};
let _instance;
const [TabList, PanelList, ready] = useDynTabs(options);
ready((instance) => {
_instance = instance;
});
const addTab3 = function () {
// open tab 3
_instance.open({id: '3', title: 'Tab 3', panelComponent: (porps) => <p> panel 3 </p>}).then(() => {
console.log('tab 3 is open');
});
// switch to tab 3
_instance.select('3').then(() => {
console.log('tab 3 is selected');
});
};
return (
<>
<button onClick={addTab3}>Add tab 3</button>
<TabList></TabList>
<PanelList></PanelList>
</>
);
};
NOTE :
-
Tabs can't be manipulated safely before the first render, use
ready()
to access the instance object,ready
accepts a function as its parameter and calls it when tabs are mounted. -
ready
function identity is stable and won’t change on re-renders.
type | default value | required | description |
---|---|---|---|
Array of tabData |
[] |
false | initial opened tabs |
Example
const [TabList, PanelList, ready] = useDynTabs({
tabs: [
{
id: '1',
title: 'home',
iconClass: 'fa fa-home',
closable: true,
panelComponent: (porps) => <p> home content </p>,
},
{
id: '2',
title: 'contact',
tooltip: 'contact',
disable: true,
closable: false,
panelComponent: (porps) => <p> contact content </p>,
},
],
});
type | default value | required | description |
---|---|---|---|
string | ' ' | false | specifies initial selected tab |
Example
const [TabList, PanelList, ready] = useDynTabs({
tabs: [
{
id: '1',
title: 'home',
iconClass: 'fa fa-home',
closable: true,
panelComponent: (porps) => <p> home content </p>,
},
{
id: '2',
title: 'contact',
tooltip: 'contact',
disable: true,
closable: false,
panelComponent: (porps) => <p> contact content </p>,
},
],
selectedTabID: '2',
});
type | default value | required | description |
---|---|---|---|
string | 'ltr' | false | can be either of 'ltr' or 'rtl' |
Example
const [TabList, PanelList, ready] = useDynTabs({direction: 'rtl'});
type | required | description |
---|---|---|
React component | false | custom tab component |
Example
const [TabList, PanelList, ready] = useDynTabs({
tabComponent: (props) => {
const {id, isSelected, api: instance} = props;
return (
<button {...props.tabProps}>
{props.children}
{props.iconProps && <span {...props.iconProps}></span>}
</button>
);
},
});
Default value for panelComponent option.
type | required | description |
---|---|---|
React component | React element | false |
Example
const [TabList, PanelList, ready] = useDynTabs({
defaultPanelComponent: (props) => {
const {id, isSelected, api: instance} = props;
return <div>loading...</div>;
},
});
type | default value | required | description |
---|---|---|---|
boolean | true | false |
Example
const [TabList, PanelList, ready] = useDynTabs({accessibility: false});
NOTE :
When accessibility option is true, it sets the id attribute of panel and button elements.
type | default value | required | description |
---|---|---|---|
boolean | false | false |
Example
const [TabList, PanelList, ready] = useDynTabs({isVertical: true});
type | required | description |
---|---|---|
function | false | This event is fired only once, after the first render |
Example
const [TabList, PanelList, ready] = useDynTabs({
onLoad: function () {
console.log('[onLoad]');
},
});
NOTE :
You can use 'this' keyword inside all callback options. It refers to the instance object.
type | required | description |
---|---|---|
function | false | This event is triggered after every render. |
Example
const [TabList, PanelList, ready] = useDynTabs({
onInit: function () {
console.log('[onInit]');
},
});
NOTE :
Do not use setState inside the onInit callback because it leads to an infinite loop.
type | required | description |
---|---|---|
function | false | fires when we open|close|select a tab. this event is not fired initially |
Example
const [TabList, PanelList, ready] = useDynTabs({
onChange: function ({currentData, previousData, closedTabIDs, openedTabIDs}) {
console.log('[onChange]');
},
});
type | required | description |
---|---|---|
function | false | fires when the user clicks on the tab, but before select them. This event should return boolean true or false, If the event return false the tab is not selected. |
Example
const [TabList, PanelList, ready] = useDynTabs({
beforeSelect: function (e, id) {
console.log('[beforeSelect]');
return true;
},
});
type | required | description |
---|---|---|
function | false | fires after selecting a tab for the first time. It is not fired for the initial selected tab |
Example
const [TabList, PanelList, ready] = useDynTabs({
onFirstSelect: function ({currentSelectedTabId, previousSelectedTabId}) {
console.log('[onFirstSelect]');
},
});
type | required | description |
---|---|---|
function | false | fires after selecting a tab. this event is not fired initially |
Example
const [TabList, PanelList, ready] = useDynTabs({
onSelect: function ({currentSelectedTabId, previousSelectedTabId}) {
console.log('[onSelect]');
},
});
type | required | description |
---|---|---|
function | false | fires after opening tabs. this event is not fired initially |
Example
const [TabList, PanelList, ready] = useDynTabs({
onOpen: function (openedTabIDs) {
console.log('[onOpen]');
},
});
type | required | description |
---|---|---|
function | false | fires when the user clicks on the close icon, but before close them. This event should return boolean true or false, If the event return false the tab is not closed. |
Example
const [TabList, PanelList, ready] = useDynTabs({
beforeClose: function (e, id) {
console.log('[beforeClose]');
return true;
},
});
type | required | description |
---|---|---|
function | false | fires after closing tabs. this event is not fired initially |
Example
const [TabList, PanelList, ready] = useDynTabs({
onClose: function (closedTabIDs) {
console.log('[onClose]');
},
});
type | required | description |
---|---|---|
function | false | fires before destroying useDynTabs hook |
Example
const [TabList, PanelList, ready] = useDynTabs({
onDestroy: function () {
console.log('[onDestroy]');
},
});
Return value : boolean
Parameters:
id: String
Example
const result = instance.isOpen('Your tab ID');
Triggers 'onInit', 'onChange' and 'onOpen' event.
It only triggers 'onInit' event, if the tab is already open.
Return value : Promise
Parameters:
tabData: Object
Example
if (instance.isOpen('contact') == false) {
instance
.open({
id: 'contact',
title: 'contact',
tooltip: 'contact',
disable: false,
closable: true,
iconClass: '',
panelComponent: <ContactPanel></ContactPanel>,
})
.then(({currentData, instance}) => {
console.log('contact tab is open');
});
}
Return value : boolean
Parameters:
id: String
Example
const result = instance.isSelected('Your tab ID');
Makes current and previous selected tab to be re-rendered
Triggers 'onInit', 'onChange' and 'onSelect' event.
It only triggers 'onInit' event, if the tab is already selected.
Return value : Promise
Parameters:
id: string
Example
if (instance.isSelected('1') == false) {
instance.select('1').then(({currentData, instance}) => {
console.log('tab 1 is selected');
});
}
Triggers 'onInit', 'onChange' and 'onClose' event.
It only triggers 'onInit' event, if the tab is already closed.
When switching parameter is true, it switches to previous selected tab
Return value : Promise
Parameters:
id: string
switching: boolean (default : true)
Example
if (instance.isOpen('2') == true) {
instance.close('2').then(({currentData, instance}) => {
console.log('tab 2 is closed');
});
}
Makes all tabs to be re-rendered.
triggers onInit event.
Return value : Promise
Example
instance.refresh().then(({currentData, instance}) => {});
Parameters:
optionName : String
Example
const direction = instance.getOption('direction');
const onSelect = instance.getOption('onSelect');
You can use this method for setting all options except selectedTabID and tabs options.
For re-rendering immediately after this function, you should call refresh method after it.
Return value : instance object
Parameters:
optionName : String
optionValue : string|boolean|object|function
Example
instance.setOption('direction', 'rtl');
instance.setOption('onSelect', () => {});
instance.setOption('beforeSelect', () => false);
get tabData by id
Return value : tabData object
Parameters:
id : String
Example
const tabData = instance.getTab('3');
console.log(tabData.id); // 3
set tabData by id. for re-rendering immediately after this function, you should call refresh method after it.
Return value : instance
Parameters:
tab id : String
source object : containing the properties you want to apply
Example
instance.setTab('home', {disable: true});
instance.setTab('contact', {closable: false, panelComponent: (props) => <p>contact panel</p>});
Attach an event handler function for one event.
Return value : instance
Parameters:
event Name : String (can be either of onFirstSelect|onSelect|onClose|onOpen|onInit|onChange|onDestroy)
handler : function
Example
const handler = React.useCallback(function (params) {
const {currentSelectedTabId, previousSelectedTabId} = params;
}, []);
instance.on('onSelect', handler);
Attach a handler to an event. The handler is executed at most once.
Return value : instance
Parameters:
event Name : String (can be either of onFirstSelect|onSelect|onClose|onOpen|onInit|onChange|onDestroy)
handler : function
Example
instance.one('onSelect', function ({currentSelectedTabId, previousSelectedTabId}) {});
Remove an event handler.
Return value : instance
Parameters:
event Name : String (can be either of onFirstSelect|onSelect|onClose|onOpen|onInit|onChange|onDestroy)
handler : function (A handler function previously attached for the event)
Example
const handler = React.useCallback(function () {}, []);
const attachHandler = () => {
instance.on('onSelect', handler);
};
const deattachHandler = () => {
instance.off('onSelect', handler);
};
get a copy of data
Return value : Object
Example
const {selectedTabID, openTabIDs} = instance.getData();
NOTE :
- getCopyData function is an older version of getData function and it is enabled by default so that existing users do not have to change their code. You are free to use both conventions.
get a copy of data in previous render
Return value : Object
Example
const {selectedTabID, openTabIDs} = instance.getPreviousData();
NOTE :
- getCopyPerviousData function is an older version of getPreviousData function and it is enabled by default so that existing users do not have to change their code. You are free to use both conventions.
Useful for sorting tabs manually.
Triggers onInit
event.
Return value : Promise
Parameters:
Array of all tabs IDs
Example
const {openTabIDs} = instance.getData();
instance.sort(openTabIDs.reverse()).then(({currentData, instance}) => {
console.log('sorting tabs has finished');
});
property name | type | default value | required | description |
---|---|---|---|---|
id | string | false | an unique identifier for each tab | |
title | string | ' ' | false | |
tooltip | string | ' ' | false | |
panelComponent | React Element | React Component | null | A function component which returns empty div | false | |
lazy | boolean | false | false | If set to false the panel will be rendered initially. if set to true the panel will not be rendered until the tab is activated |
closable | boolean | true | false | |
iconClass | string | ' ' | false | class name for the icon |
disable | boolean | false | false |
Example
const tabData = {
id: 'contactID',
title: 'contactTitle',
tooltip: 'contactTooltip',
disable: true,
lazy: true,
iconClass: 'fa fa-home',
closable: false,
panelComponent: (porps) => <p> contact content </p>,
};
const [TabList, PanelList, ready] = useDynTabs({tabs: [tabData]});
// or
if (instance.isOpen(tabData.id) == false) {
instance.open(tabData).then(() => {});
}
Defer loading of tab content until the tab is activated
Example 1
const Panel3 = React.lazy(() => import('./components/panel3.js'));
function LazyLoadingPanel3(porps) {
return (
<Suspense fallback={<div>Loading...</div>}>
<Panel3 {...porps}></Panel3>
</Suspense>
);
}
useDynTabs({
tabs: [
{id: '1', title: 'eager loading tab 1', panelComponent: <p>panel 1</p>},
{id: '2', title: 'eager loading tab 2', lazy: true, panelComponent: <p>panel 2</p>},
{id: '3', title: 'lazy loading tab 3', lazy: true, panelComponent: LazyLoadingPanel3},
],
selectedTabID: '1',
});
NOTE :
- panel 1 is eagerly loaded and rendered.
- panel 2 is eagerly loaded but will not be rendered until tab 2 is activated.
- panel 3 will not be loaded and rendered until tab 3 is activated.
Example 2 ( using onFirstSelect event )
useDynTabs({
tabs: [
{id: '1', title: 'eager loading tab 1', panelComponent: <p>panel 1</p>},
{id: '2', title: 'eager loading tab 2', lazy: true, panelComponent: <p>panel 2</p>},
{id: '3', title: 'lazy loading tab 3', lazy: true},
],
selectedTabID: '1',
defaultPanelComponent: function DefaultPanel() {
return <div>loading...</div>;
},
onFirstSelect: function ({currentSelectedTabId}) {
const instance = this;
if (currentSelectedTabId === '3') {
import('path to/panel3.js').then((defaultExportedModule) => {
const Panel3 = defaultExportedModule.default;
instance.setTab('3', {panelComponent: Panel3});
instance.refresh();
});
}
},
});
react-dyn-tabs does not include any style loading by default. Default stylesheets and themes are provided and can be included in your application if desired.
import 'react-dyn-tabs/style/react-dyn-tabs.min.css';
// or import 'react-dyn-tabs/style/scss/react-dyn-tabs.scss';
import 'react-dyn-tabs/themes/react-dyn-tabs-card.min.css';
// or import 'react-dyn-tabs/themes/scss/react-dyn-tabs-card.scss';
NOTE :
You can find other themes at themes folder and multiple themes example at example/multi-themes-example folder.
-
Some actions like open, select, close and refresh cause re-rendering, and using them immediately after calling useDynTabs hook will create an infinite loop and other bugs that most likely you don't want to cause. you should use them inside event listeners or subscriptions.
-
Do not use setState inside the onInit callback because it leads to an infinite loop.
These deprecated features can still be used, but should be used with caution because they are expected to be removed entirely sometime in the future. You should work to remove their use from your code.
-
Third element of returned array by useDynTabs hook should not be used as an object, it is no longer recommended and only be kept for backwards compatibility purposes, it should be used as a function.
-
First parameter of onSelect function is an object and has perviousSelectedTabId property which is deprecated. you should use previousSelectedTabId property instead of perviousSelectedTabId property.
-
First parameter of onChange function is an object and has perviousData property which is deprecated. you should use previousData property instead of perviousData property.
$ npm run test
MIT