I have a requirement where i need to create a web app in nextjs that has tabs components in it.
The idea is that this will handle multiple entities in each tab, for example i have a search bar to select diferent products, when i select one, a new tab is created with that product info as well as forms which the user will manually fill.
Each time i select a new product a new tab is opened but i should not loose what i entered in the first tab.
I need to be able to switch between tabs without loosing state.
I want to know is there any simple straight forward way? a simple example will be great.
I have achieved this using zustand and store each tab content changes in a variable then fetch it when ever a tab is opened, but it looks dirty and not maintainable.
Handling dynamically created tabs and their state in Next.js can be streamlined using React's state management and context API.
First, create a context to manage the state of the tabs.
// contexts/TabsContext.js
import { createContext, useContext, useState } from 'react';
const TabsContext = createContext();
export const useTabs = () => useContext(TabsContext);
export const TabsProvider = ({ children }) => {
const [tabs, setTabs] = useState([]);
const [activeTab, setActiveTab] = useState(null);
const addTab = (tab) => {
setTabs([...tabs, tab]);
setActiveTab(tab.id);
};
const removeTab = (id) => {
setTabs(tabs.filter(tab => tab.id !== id));
if (activeTab === id) {
setActiveTab(tabs.length > 0 ? tabs[0].id : null);
}
};
const switchTab = (id) => {
setActiveTab(id);
};
return (
<TabsContext.Provider value={{ tabs, activeTab, addTab, removeTab, switchTab }}>
{children}
</TabsContext.Provider>
);
};
This component will handle the rendering of tabs and the forms inside each tab.
// components/Tabs.js
import { useTabs } from '../contexts/TabsContext';
const Tabs = () => {
const { tabs, activeTab, addTab, removeTab, switchTab } = useTabs();
return (
<div>
<div className="tab-list">
{tabs.map(tab => (
<button key={tab.id} onClick={() => switchTab(tab.id)}>
{tab.title}
<span onClick={() => removeTab(tab.id)}>x</span>
</button>
))}
<button onClick={() => addTab({ id: Date.now(), title: 'New Tab', content: '' })}>
+ New Tab
</button>
</div>
<div className="tab-content">
{tabs.map(tab => (
tab.id === activeTab ? <div key={tab.id}>{tab.content}</div> : null
))}
</div>
</div>
);
};
export default Tabs;
Each tab will contain a form whose data needs to be persisted.
// components/TabForm.js
import { useState } from 'react';
import { useTabs } from '../contexts/TabsContext';
const TabForm = ({ tabId }) => {
const { tabs, setTabs } = useTabs();
const tab = tabs.find(tab => tab.id === tabId);
const [formData, setFormData] = useState(tab ? tab.content : '');
const handleChange = (e) => {
setFormData(e.target.value);
setTabs(tabs.map(tab => tab.id === tabId ? { ...tab, content: e.target.value } : tab));
};
return (
<form>
<textarea value={formData} onChange={handleChange} />
</form>
);
};
export default TabForm;
Wrap your main application component with the TabsProvider to provide the context to all components.
// pages/_app.js
import { TabsProvider } from '../contexts/TabsContext';
function MyApp({ Component, pageProps }) {
return (
<TabsProvider>
<Component {...pageProps} />
</TabsProvider>
);
}
export default MyApp;
Finally, use the Tabs component in a page to see it in action.
// pages/index.js
import Tabs from '../components/Tabs';
export default function Home() {
return (
<div>
<h1>Dynamic Tabs Example</h1>
<Tabs />
</div>
);
}