Creating a custom Modal for Material-Table - reactjs

I am trying to create a custom button on my Material-Table that will pop up a model that will allow users to make changes. The issue I am running into is the button is not toggling to true when clicked upon. I am using a custom Modal hook. The modal itself work, because when it is explicitly set to true it pops up on the screen
here is the modal hook
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
function toggle() {
setIsShowing(!isShowing);
}
return {
isShowing,
toggle,
}
};
the modal component
const Modal = ({ isShowing, hide }) => isShowing ? ReactDOM.createPortal(
<React.Fragment>
<div className="modal-overlay"/>
<div className="modal-wrapper" aria-modal aria-hidden tabIndex={-1} role="dialog">
<div className="modal">
<div className="modal-header">
<button type="button" className="modal-close-button" data-dismiss="modal" aria-label="Close" onClick={hide}>
<span aria-hidden="true">×</span>
</button>
</div>
<p>
Hello, I'm a modal.
</p>
</div>
</div>
</React.Fragment>, document.body
) : null;
and finally the actual table component
columns: [
{ title: "Department", field: "department" },
{ title: "Security", field: "security", type: "numeric" },
{ title: "Status", field: "status" },
{
title: "Actions",
field: "actions",
render: rowData => (
<div className="edit-button">
<span className={" fas fa-pencil-alt"} onClick={toggle} />
{console.log(isShowing)}
<Modal isShowing={isShowing} hide={toggle} />
</div>
)
}
],
data: [{ department: "Admin", security: 10, status: "Active" }]
});
<MaterialTable
title="Departments"
columns={state.columns}
data={state.data}
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
})
}}
/>
I need the isShowing to toggle between true and false

Related

Global Filter in react-table v8 isn't working

I have a slightly modified implementation of the react-table v8 filters: https://tanstack.com/table/v8/docs/examples/react/filters
In the original they seem to use a custom filter function to filter the table globally but it requires another library that i don't want to include, the documentation isn't very clear about this but there seems to be built-in functions that i can use: https://tanstack.com/table/v8/docs/api/features/filters
However the table stops filtering as soon as i change it, i tried not including the globalFilterFn as well as setting it to globalFilterFn: "includesString" which is one of the built-in functions i mentioned but nothing has worked so far.
here is my code:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import {
useReactTable,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
FilterFn,
ColumnDef,
flexRender
} from "#tanstack/react-table";
//import { RankingInfo, rankItem } from "#tanstack/match-sorter-utils";
import { makeData, Person } from "./makeData";
/* declare module "#tanstack/table-core" {
interface FilterMeta {
itemRank: RankingInfo;
}
}
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value);
// Store the itemRank info
addMeta({
itemRank
});
// Return if the item should be filtered in/out
return itemRank.passed;
}; */
function App() {
const rerender = React.useReducer(() => ({}), {})[1];
const [globalFilter, setGlobalFilter] = React.useState("");
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [
{
header: "Name",
footer: (props) => props.column.id,
columns: [
{
accessorKey: "firstName",
cell: (info) => info.getValue(),
footer: (props) => props.column.id
},
{
accessorFn: (row) => row.lastName,
id: "lastName",
cell: (info) => info.getValue(),
header: () => <span>Last Name</span>,
footer: (props) => props.column.id
},
{
accessorFn: (row) => `${row.firstName} ${row.lastName}`,
id: "fullName",
header: "Full Name",
cell: (info) => info.getValue(),
footer: (props) => props.column.id
}
]
},
{
header: "Info",
footer: (props) => props.column.id,
columns: [
{
accessorKey: "age",
header: () => "Age",
footer: (props) => props.column.id
},
{
header: "More Info",
columns: [
{
accessorKey: "visits",
header: () => <span>Visits</span>,
footer: (props) => props.column.id
},
{
accessorKey: "status",
header: "Status",
footer: (props) => props.column.id
},
{
accessorKey: "progress",
header: "Profile Progress",
footer: (props) => props.column.id
}
]
}
]
}
],
[]
);
const [data, setData] = React.useState(() => makeData(500));
const refreshData = () => setData((old) => makeData(500));
const table = useReactTable({
data,
columns,
state: {
globalFilter
},
onGlobalFilterChange: setGlobalFilter,
//globalFilterFn: "includesString",
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel()
});
return (
<div className="p-2">
<div>
<input
value={globalFilter ?? ""}
onChange={(event) => setGlobalFilter(event.target.value)}
className="p-2 font-lg shadow border border-block"
placeholder="Search all columns..."
/>
</div>
<div className="h-2" />
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
<div
{...{
className: header.column.getCanSort()
? "cursor-pointer select-none"
: "",
onClick: header.column.getToggleSortingHandler()
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{{
asc: " 🔼",
desc: " 🔽"
}[header.column.getIsSorted() as string] ?? null}
</div>
</>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<div className="h-2" />
<div className="flex items-center gap-2">
<button
className="border rounded p-1"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
{"<<"}
</button>
<button
className="border rounded p-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{"<"}
</button>
<button
className="border rounded p-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{">"}
</button>
<button
className="border rounded p-1"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
{">>"}
</button>
<span className="flex items-center gap-1">
<div>Page</div>
<strong>
{table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</strong>
</span>
<span className="flex items-center gap-1">
| Go to page:
<input
type="number"
defaultValue={table.getState().pagination.pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
table.setPageIndex(page);
}}
className="border p-1 rounded w-16"
/>
</span>
<select
value={table.getState().pagination.pageSize}
onChange={(e) => {
table.setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
<div>{table.getPrePaginationRowModel().rows.length} Rows</div>
<div>
<button onClick={() => rerender()}>Force Rerender</button>
</div>
<div>
<button onClick={() => refreshData()}>Refresh Data</button>
</div>
<pre>{JSON.stringify(table.getState(), null, 2)}</pre>
</div>
);
}
const rootElement = document.getElementById("root");
if (!rootElement) throw new Error("Failed to find the root element");
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
a link to CodeSanbox with it: https://codesandbox.io/s/long-monad-652jcm?file=/src/main.tsx
I'm still very inexperienced in React and even more with typescript so maybe i'm missing something obvious.
am i misinterpreting the docs, maybe the custom function is necessary?
There is a current issue on GitHub where you can't use a global filter if one of your column is using number fields. You can simply solve it by replacing the number by a string in your data. (.toString(), and sorting still works). If worked fine for me afterwards. :)
Your globalFilter doesn't do anything, you need to test if the contents of the table are equal to the filter or not.

How to add a child element when clicking the cell in Ag-grid?

I'm trying to add an icon when clicking on the cell. When I click on a cell by using teamFunction in cellRenderer it shows info about the cell. But there is also the method onCellClicked which shows info about the clicked cell too. Both methods are clickable. I need to add an icon when clicking on the cell. I want to return div with icon to the selected cell:
In the cell I need to add the child element:
<div className="checked">
<FontAwesomeIcon icon={faCheck} color="green"/>
</div>
In the teams array I have objects with info:
const teams = [
{
gender: "boys"
name: "High School Team"
sport: "Volleyball"
team_id: "12111"
},
{
gender: "girls"
name: "Middle School Team"
sport: "Baseball"
team_id: "12222"
},
{
gender: "boys"
name: "Students"
sport: "Bowling"
team_id: "12333"
},
]
Component:
const onFirstDataRendered = useCallback(() => {
ref.current.api.sizeColumnsToFit();
}, []);
const teamFunction = (params) => {
return (
<div className="cell">
<div>
<div>{params.data.gender} {params.data.sport}</div>
<div>{params.data.name}</div>
</div>
</div>
);
};
const columnDefs = [
{
field: 'team',
headerName: 'Team',
cellRenderer: teamFunction,
resizable: true,
width: 100,
},
];
return (
<AgGridReact
ref={ref}
rowData={teams}
columnDefs={columnDefs}
defaultColDef={{ resizable: true }}
rowHeight={60}
headerHeight={50}
onFirstDataRendered={onFirstDataRendered}
onGridSizeChanged={() => onGridSizeChanged(ref)}
/>
I added Hook
const [selectedTeam, setSelectedTeam] = useState(null);
In teamFunction I control selectedTeam:
const teamFunction = (params) => {
return (
<div className={styles.cellStyles}>
<div className={(selectedTeam !== params.data.team_id) ? styles.unSelectedTeam : null}>
<div className={styles.boldText}>{params.data.gender}{params.data.sport}</div>
<div>{params.data.name}</div>
</div>
{(selectedTeam === params.data.team_id) ? (
<div className={styles.checked}>
<FontAwesomeIcon icon={faCheck} color="#22EA72" style={{ fontWeight: 'bold' }} />
</div>
) : null}
</div>
);
};
In columnDefs added onCellClicked:
onCellClicked: cellClicked,
cellClicked function controls the selected team :
const cellClicked = (params) => {
setSelectedTeam(params.data.team_id);
};

repeater field repeating incorrectly

I want to click a button to add a tab and then have content inside the tab that can also be repeated. Currently the part where I click on the button to add a new tab is working however entering content in tab one duplicates it in tab 2 once I create another tab ie: the inside content of tab 1 and 2 seems linked when it shouldn't be. I should be able to create multiple tabs and then enter unique data inside each tab.
If I have explained this poorly please let me know and I will elaborate further. I think it perhaps needs to be an array of objects within an array of objects.
registerBlockType("blocks/tabs", {
title: __("Tabbed Blocks", "custom-blocks"),
description: __("Tabbed content blocks", "custom-blocks"),
icon: "smiley",
category: "custom-category",
keywords: [__("tabs", "custom-blocks"), __("repeat", "custom-blocks")],
attributes: {
tabs: {
type: "array",
default: [""],
},
tiles: {
type: "array",
default: [""],
},
},
edit: (props) => {
const {
attributes: { tabs, tiles },
setAttributes,
} = props;
const [showTab, setShowTab] = useState("");
const handleTabClick = (index) => {
console.log(index);
setShowTab(index);
};
return (
<>
<div className="tabs">
<ul id="tabs-nav">
{tabs.map((t, index) => (
<li>
<span onClick={() => handleTabClick(index)} id={`#tab${index}`}>
<RichText
placeholder="Tab title"
onChange={(tabTitle) => {
const newObject = Object.assign({}, t, {
tabTitle: tabTitle,
});
setAttributes({
tabs: [
...tabs.filter((item) => item.index != t.index),
newObject,
],
});
}}
value={t.tabTitle}
/>
</span>
</li>
))}
</ul>
{tabs.map((t, index) => (
<div
id={`tab${index}`}
className={
showTab == index ? `tab-content show` : `tab-content hide`
}
>
<div className="home-tabs">
{tiles.map((tile, index) => (
<div
className="tab-block"
>
<div className="copy">
<RichText
tagName="h3"
placeholder="Tab Content Title"
onChange={(tabTileTitle) => {
const newObject = Object.assign({}, tile, {
tabTileTitle: tabTileTitle,
});
setAttributes({
tiles: [
...tiles.filter(
(item) => item.index != tile.index
),
newObject,
],
});
}}
value={tile.tabTileTitle}
/>
<p>
Some content...
</p>
</div>
</div>
))}
</div>
</div>
))}
<Button
onClick={() => {
setAttributes({
tiles: [...tiles, { index: tiles.length }],
});
}}
>
Add Tile
</Button>
</div>
<Button
onClick={() => {
setAttributes({
tabs: [...tabs, { index: tabs.length }],
});
console.log(tabs);
}}
>
Add Tab
</Button>
</>
);
},
save: (props) => {
return null;
},
});

Why arent these functions within the main functional component?

I don't understand why these formatter functions are placed outside the main component called TemplateTable.
If you need to use the actionformatter for example, you cannot pass a dispatch function do it correct?
I had an issue trying to set the actionformatter onClick for the delete button to a dispatch(deleteTemplate()).
When I run the code while the actionformatter is outside the main component TemplateTable, I get dispatch is undefined. When I defined dispatch within the component I obviously get the cannot use react hooks ouside function component problem.
I can fix this whole issue by just including the actionformatter inside the block of templateTable. I just feel like im shortcutting and was wondering if anyone had any input on this
import React, { createRef, Fragment, useState, useEffect} from 'react';
import {
Button,
Card,
CardBody,
Col,
CustomInput,
DropdownItem,
DropdownMenu,
DropdownToggle,
InputGroup,
Media,
Modal,
ModalBody,
Row,
UncontrolledDropdown
} from 'reactstrap';
import { connect, useDispatch } from 'react-redux';
import FalconCardHeader from '../common/FalconCardHeader';
import ButtonIcon from '../common/ButtonIcon';
import paginationFactory, { PaginationProvider } from 'react-bootstrap-table2-paginator';
import BootstrapTable from 'react-bootstrap-table-next';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { Link } from 'react-router-dom';
import Flex from '../common/Flex';
import Avatar from '../common/Avatar';
import { getPaginationArray } from '../../helpers/utils';
import CreateTemplate from '../templates/CreateTemplate';
import customers from '../../data/e-commerce/customers';
import { listTemplates, deleteTemplate } from '../../actions/index';
const nameFormatter = (dataField, { template }) => {
return (
<Link to="/pages/customer-details">
<Media tag={Flex} align="center">
<Media body className="ml-2">
<h5 className="mb-0 fs--1">{template}</h5>
</Media>
</Media>
</Link>
);
};
const bodyFormatter = (dataField, { avatar, body }) => {
return (
<Link to="/pages/customer-details">
<Media tag={Flex} align="center">
<Media body className="ml-2">
<h5 className="mb-0 fs--1">{body}</h5>
</Media>
</Media>
</Link>
);
};
const emailFormatter = email => <a href={`mailto:${email}`}>{email}</a>;
const phoneFormatter = phone => <a href={`tel:${phone}`}>{phone}</a>;
const actionFormatter = (dataField, { _id }) => (
// Control your row with this id
<UncontrolledDropdown>
<DropdownToggle color="link" size="sm" className="text-600 btn-reveal mr-3">
<FontAwesomeIcon icon="ellipsis-h" className="fs--1" />
</DropdownToggle>
<DropdownMenu right className="border py-2">
<DropdownItem onClick={() => console.log('Edit: ', _id)}>Edit</DropdownItem>
<DropdownItem onClick={} className="text-danger">
Delete
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
);
const columns = [
{
dataField: 'name',
text: 'Name',
headerClasses: 'border-0',
classes: 'border-0 py-2 align-middle',
formatter: nameFormatter,
sort: true
},
{
dataField: 'content',
headerClasses: 'border-0',
text: 'Content',
classes: 'border-0 py-2 align-middle',
formatter: bodyFormatter,
sort: true
},
{
dataField: 'joined',
headerClasses: 'border-0',
text: 'Last modified',
classes: 'border-0 py-2 align-middle',
sort: true,
align: 'right',
headerAlign: 'right'
},
{
dataField: '',
headerClasses: 'border-0',
text: 'Actions',
classes: 'border-0 py-2 align-middle',
formatter: actionFormatter,
align: 'right'
}
];
const SelectRowInput = ({ indeterminate, rowIndex, ...rest }) => (
<div className="custom-control custom-checkbox">
<input
className="custom-control-input"
{...rest}
onChange={() => {}}
ref={input => {
if (input) input.indeterminate = indeterminate;
}}
/>
<label className="custom-control-label" />
</div>
);
const selectRow = onSelect => ({
mode: 'checkbox',
columnClasses: 'py-2 align-middle',
clickToSelect: false,
selectionHeaderRenderer: ({ mode, ...rest }) => <SelectRowInput type="checkbox" {...rest} />,
selectionRenderer: ({ mode, ...rest }) => <SelectRowInput type={mode} {...rest} />,
headerColumnStyle: { border: 0, verticalAlign: 'middle' },
selectColumnStyle: { border: 0, verticalAlign: 'middle' },
onSelect: onSelect,
onSelectAll: onSelect
});
const TemplateTable = ( props ) => {
let table = createRef();
// State
const [isSelected, setIsSelected] = useState(false);
const [showTemplateModal, setShowTemplateModal] = useState(false);
const handleNextPage = ({ page, onPageChange }) => () => {
onPageChange(page + 1);
};
const dispatch = useDispatch()
const deleteHandler = (_id) => {
dispatch(deleteHandler())
}
useEffect(() => {
dispatch(listTemplates())
}, [])
const handlePrevPage = ({ page, onPageChange }) => () => {
onPageChange(page - 1);
};
const onSelect = () => {
setImmediate(() => {
setIsSelected(!!table.current.selectionContext.selected.length);
});
};
const options = {
custom: true,
sizePerPage: 12,
totalSize: props.templates.length
};
return (
<Card className="mb-3">
<FalconCardHeader light={false}>
{isSelected ? (
<InputGroup size="sm" className="input-group input-group-sm">
<CustomInput type="select" id="bulk-select">
<option>Bulk actions</option>
<option value="Delete">Delete</option>
<option value="Archive">Archive</option>
</CustomInput>
<Button color="falcon-default" size="sm" className="ml-2">
Apply
</Button>
</InputGroup>
) : (
<Fragment>
<ButtonIcon onClick={(() => setShowTemplateModal(true))}icon="plus" transform="shrink-3 down-2" color="falcon-default" size="sm">
New Template
</ButtonIcon>
<Modal isOpen={showTemplateModal} centered toggle={() => setShowTemplateModal(!showTemplateModal)}>
<ModalBody className="p-0">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<CreateTemplate />
</CardBody>
</Card>
</ModalBody>
</Modal>
<ButtonIcon icon="fa-download" transform="shrink-3 down-2" color="falcon-default" size="sm" className="mx-2">
Download
</ButtonIcon>
<ButtonIcon icon="external-link-alt" transform="shrink-3 down-2" color="falcon-default" size="sm">
Expand View
</ButtonIcon>
</Fragment>
)}
</FalconCardHeader>
<CardBody className="p-0">
<PaginationProvider pagination={paginationFactory(options)}>
{({ paginationProps, paginationTableProps }) => {
const lastIndex = paginationProps.page * paginationProps.sizePerPage;
return (
<Fragment>
<div className="table-responsive">
<BootstrapTable
ref={table}
bootstrap4
keyField="_id"
data={props.templates}
columns={columns}
selectRow={selectRow(onSelect)}
bordered={false}
classes="table-dashboard table-striped table-sm fs--1 border-bottom border-200 mb-0 table-dashboard-th-nowrap"
rowClasses="btn-reveal-trigger border-top border-200"
headerClasses="bg-200 text-900 border-y border-200"
{...paginationTableProps}
/>
</div>
<Row noGutters className="px-1 py-3 flex-center">
<Col xs="auto">
<Button
color="falcon-default"
size="sm"
onClick={handlePrevPage(paginationProps)}
disabled={paginationProps.page === 1}
>
<FontAwesomeIcon icon="chevron-left" />
</Button>
{getPaginationArray(paginationProps.totalSize, paginationProps.sizePerPage).map(pageNo => (
<Button
color={paginationProps.page === pageNo ? 'falcon-primary' : 'falcon-default'}
size="sm"
className="ml-2"
onClick={() => paginationProps.onPageChange(pageNo)}
key={pageNo}
>
{pageNo}
</Button>
))}
<Button
color="falcon-default"
size="sm"
className="ml-2"
onClick={handleNextPage(paginationProps)}
disabled={lastIndex >= paginationProps.totalSize}
>
<FontAwesomeIcon icon="chevron-right" />
</Button>
</Col>
</Row>
</Fragment>
);}
}
</PaginationProvider>
</CardBody>
</Card>
);
};
const mapStateToProps = (state) => {
return {
templates: state.templates,
auth: state.auth,
deleteTemplate: state.deleteTemplate
}
}
export default connect(mapStateToProps, { listTemplates })(TemplateTable);
The short answer is that as-is there isn't any dependency on anything from a consuming component, so it makes complete sense to externalize these declarations from the TemplateTable component.
While you could just move code back into the component to close over any dependencies in the enclosure of the functional component body I think we can do better.
I suggest currying the dispatch function to any of the specific formatters that need it, i.e.
const actionFormatter = ({ dispatch }) => (dataField, { _id }) => (
// Control your row with this id
<UncontrolledDropdown>
<DropdownToggle color="link" size="sm" className="text-600 btn-reveal mr-3">
<FontAwesomeIcon icon="ellipsis-h" className="fs--1" />
</DropdownToggle>
<DropdownMenu right className="border py-2">
<DropdownItem onClick={() => console.log('Edit: ', _id)}>Edit</DropdownItem>
<DropdownItem
onClick={() => dispatch(deleteTemplate())}
className="text-danger"
>
Delete
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
);
And turn columns into a factory function in order to receive and pass on configurations, i.e.
const columns = ({ dispatch }) => ([ // <-- consume config & destructure
{
dataField: 'name',
text: 'Name',
headerClasses: 'border-0',
classes: 'border-0 py-2 align-middle',
formatter: nameFormatter,
sort: true
},
{
dataField: 'content',
headerClasses: 'border-0',
text: 'Content',
classes: 'border-0 py-2 align-middle',
formatter: bodyFormatter,
sort: true
},
{
dataField: 'joined',
headerClasses: 'border-0',
text: 'Last modified',
classes: 'border-0 py-2 align-middle',
sort: true,
align: 'right',
headerAlign: 'right'
},
{
dataField: '',
headerClasses: 'border-0',
text: 'Actions',
classes: 'border-0 py-2 align-middle',
formatter: actionFormatter({ dispatch }), // <-- pass dispatch
align: 'right'
}
]);
Create the columns configuration object and pass to the table.
const config = { dispatch };
...
<BootstrapTable
ref={table}
bootstrap4
keyField="_id"
data={props.templates}
columns={columns(config)} // <-- pass config to factory
selectRow={selectRow(onSelect)}
bordered={false}
classes="table-dashboard table-striped table-sm fs--1 border-bottom border-200 mb-0 table-dashboard-th-nowrap"
rowClasses="btn-reveal-trigger border-top border-200"
headerClasses="bg-200 text-900 border-y border-200"
{...paginationTableProps}
/>

Setting the state on one item only

How can I reverse or toggle the state only the item clicked. So far all of the tab classes are toggling to active. But I need only the one clicked. I'm teaching myself hooks. so no class component solutions please
const App = () => {
const tabItems = [
{
id:"1",
tabname: "Unsplash",
datatab: "unsplash",
template: <Unsplash/>
},
{
id:"2",
tabname: "YouTube",
datatab: "youtube",
template: <Youtube/>
},
{
id:"3",
tabname: "Wikipedia",
datatab: "wiki",
template: <Wikipedia/>
},
{
id:"4",
tabname: "DropdownApp",
datatab: "dropdownapp",
template: <DropdownApp/>
},
]
const [activeTab, setActiveTab] = useState(false)
const tabs = tabItems.map((tab, i) => {
return (
<span
className={`item ${activeTab ? 'active' : ''}`}
key={tab.id}
data-tab={tab.datatab}
onClick={() => setActiveTab(!activeTab)}>
{tab.tabname}
</span>
)
})
const tabPanels = tabItems.map((tabPanel) => {
return (
<div
key={tabPanel.id}
className={`ui bottom attached tab segment ${activeTab ? 'active' : ''}`}
data-tab={tabPanel.datatab}>
{tabPanel.template}
</div>
)
})
return (
<div className="App">
<div className="ui main text" style={{padding: '20px'}}>
<div className="ui top attached tabular menu">
{tabs}
</div>
{tabPanels}
</div>
</div>
);
}
You are only tracking if the tab are selected or not, not which one, since you have one state for all tabs. You need to track, which tab is selected:
const App = () => {
const tabItems = [
{
id:"1",
tabname: "Unsplash",
datatab: "unsplash",
template: <Unsplash/>
},
{
id:"2",
tabname: "YouTube",
datatab: "youtube",
template: <Youtube/>
},
{
id:"3",
tabname: "Wikipedia",
datatab: "wiki",
template: <Wikipedia/>
},
{
id:"4",
tabname: "DropdownApp",
datatab: "dropdownapp",
template: <DropdownApp/>
},
]
const [activeTab, setActiveTab] = useState("") // Track the id
const tabs = tabItems.map((tab, i) => {
return (
<span
className={`item ${activeTab === tab.id ? 'active' : ''}`} // Check if the tab ids are the same
key={tab.id}
data-tab={tab.datatab}
onClick={() => setActiveTab(tab.id)}> // Save the id instead of a boolean
{tab.tabname}
</span>
)
})
const tabPanels = tabItems.map((tabPanel) => {
return (
<div
key={tabPanel.id}
className={`ui bottom attached tab segment ${activeTab === tab.id ? 'active' : ''}`}
data-tab={tabPanel.datatab}>
{tabPanel.template}
</div>
)
})
return (
<div className="App">
<div className="ui main text" style={{padding: '20px'}}>
<div className="ui top attached tabular menu">
{tabs}
</div>
{tabPanels}
</div>
</div>
);
}
If you want to unselect a tab, you would need to modify the onClick:
setTab = (id) => {
setActiveTab(tab => tab === id ? "" : id);
}
and invoke it with:
<span
onClick={() => setTab(tab.id)}>

Resources