Reputation: 53
When I check the tab in the checkbox, the url is created according to the id. I want to show only that tab when the url checked. But the problem is I don't know how to create url when more than two tabs are checked. What logic or library do I need?
function App() {const [tabs, setTabs] = useState([
{ id: 'XYZ', name: 'Prediction', active: true, checked: true },
{ id: 'XY1', name: 'Model', active: false, checked: false },
{ id: 'XY2', name: 'Model2', active: false, checked: false }]);
const [activeTab, setActiveTab] = useState('XYZ');
const selectUrlCheck = (e, id) => {
const checkedIndex = tabs.findIndex(tab => tab.id === id);
const newTabs = [...tabs];
if (newTabs[checkedIndex].checked === true) {
newTabs[checkedIndex] = { ...newTabs[checkedIndex], checked: false };
setTabs([...newTabs]);
} else {
newTabs[checkedIndex] = { ...newTabs[checkedIndex], checked: true };
setTabs([...newTabs]);
}};
const tabToggle = e => {
setActiveTab(e.target.id);
};
return (
<AppWrapper>
<TopNavigation
tabs={tabs}
selectUrlCheck={selectUrlCheck}
tabToggle={tabToggle}
activeTab={activeTab}
url={window.location.href}
/>
<ContentWrapper>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route
exact
path="/"
render={() => <Redirect to={`/predict/${activeTab}`} />}
/>
<Route path="/predict" component={PredictPageRouter} />
<Route path="/preview" component={PreviewPage} />
</Switch>
</Suspense>
</ContentWrapper>
</AppWrapper>
);
}
const TopNavigation = ({ tabs, tabToggle, activeTab, url, selectUrlCheck }) => {
const [publishingModal, setPublishingModal] = useState(false);
const modalOpen = () => {
console.log('modal Open');
setPublishingModal(true);
};
return (
<>
<PublishingModal
url={url}
tabs={tabs}
selectUrlCheck={selectUrlCheck}
open={publishingModal}
onCloseModal={setPublishingModal}
/>
<Wrapper>
<TopBarWrapper>
<LogoWrapper>
<LogoLink to="/">
<Img
src={AlgoLogo}
alt="logo"
style={{ width: '27px', height: '27px', marginTop: '-5px' }}
/>
<LogoText>dashboard</LogoText>
</LogoLink>
</LogoWrapper>
</TopBarWrapper>
<TopTabWRapper>
<TabWrapper>
{tabs &&
tabs.map(({ id, name }) => (
<LogoTab to={`/predict/${id}`} key={id}>
<TabLogoText
id={id}
onClick={e => tabToggle(e)}
className={activeTab === id ? 'active' : ''}
>
{name}{' '}
</TabLogoText>
</LogoTab>
))}
</TabWrapper>
<div>
<PublishingBtn onClick={modalOpen}>
<Icon name="share alternate" />
<span>publishing url</span>
</PublishingBtn>
<GoToModelDataLink>
<Icon name="setting" />
management
</GoToModelDataLink>
</div>
</TopTabWRapper>
</Wrapper>
</>
);
};
const PublishingModal = ({ open, onCloseModal, url, tabs, selectUrlCheck }) => {
if (!open) return null;
return (
<>
<OverLay />
<Bg>
<Root>
<ModalHeader>
<IconImg
role="button"
src={CloseIcon}
alt="close modal"
onClick={() => onCloseModal(false)}
/>
</ModalHeader>
<ModalContent>
<TitleArea>
<Title>publishing</Title>
</TitleArea>
<ShareContent>
<ClipBoardInput
value={url}
/>
<CopyToClipboard text={url}>
<CopyClipBtn>
<Icon name="copy" />
</CopyClipBtn>
</CopyToClipboard>
<CopyToClipboard text={url}>
<ShareBtn>
<Icon name="share alternate" />
<span>share</span>
</ShareBtn>
</CopyToClipboard>
</ShareContent>
<SelectH2>Select pages to publish</SelectH2>
<CheckBoxWrapper>
{tabs.map(tab => (
<TabWrapper key={tab.id}>
<UrlCheckbox
name={tab.name}
id={tab.id}
checked={tab.checked}
onChange={selectUrlCheck}
label="checkbox"
/>
<ModelSpan>{tab.name}</ModelSpan>
{* UrlBox component is checkbox *}
<UrlBox>{`${window.location.origin}/predict/${
tab.id
}`}</UrlBox>
</TabWrapper>
))}
</CheckBoxWrapper>
</ModalContent>
</Root>
</Bg>
</>
);
};
In modal, there is the url of each tab in the check box, and if you check the check box, a new url is created, and if you share the URL to the customer accordingly, only the checked tab is rendering.
Upvotes: 0
Views: 751
Reputation: 42218
There's a lot of ways to approach this. You could use query parameters like "/predict/?model=XYZ,XY1"
. You could use a stable URL like "/predict"
and pass the models as state
.
The code that I am going to create here uses a combined slug. A single checked tab would lead to "/predict/XYZ"
while checking multiple tabs creates a URL like "/predict/XY1-XYZ
. This format assumes that the id
itself never contains a hyphen -
. If that is incorrect then you can rewrite these functions to something different.
// convert an array of ids into a string slug
function idsToSlug( ids = [] ) {
// I am sorting them to enforce consistent slugs regardless of the order of the array
// [...ids] prevents mutation by sorting a copied array
return [...ids].sort().join('-');
}
// convert a string slug into an array of ids
function slugToIds( slug = "" ) {
return slug.split('-').filter( s => s.length > 0 );
}
Our PredictPage
component can get the slug from the URL and use it to lookup which tabs to include.
function PredictPageRouter() {
// this will be a string
const {slug = ""} = useParams();
// break string into array
const activeIds = slugToIds(slug);
/* ... */
}
I am confused about why you have the tabs themselves in a useState
. Are they a constant? Or do they come from some external source that changes? If it's a changing state then how does your PredictPageRouter
work when aren't passing it tabs
as a prop? I am going to assume that only active
and checked
are stateful and that the tabs themselves are some app-wide constant.
const TABS = [
{ id: 'XYZ', name: 'Prediction' },
{ id: 'XY1', name: 'Model' },
{ id: 'XY2', name: 'Model2' },
];
Assuming a global TABS
constant, we can search for a tab by id
.
function findTab( id ) {
const tab = TABS.find(
// case-insensitive matching
tab => tab.id.toLowerCase() === id.toLowerCase()
)
// throw an error if tab not found
if ( ! tab ) {
throw new Error("invalid tab id " + id);
}
return tab;
}
Here's a dummy component to check the routing.
function TabContent({activeIds}) {
// lookup tab objects
try {
const tabs = activeIds.map(findTab);
// do whatever with the tabs
return (
<div>Showing Tabs: {tabs.map(tab => tab?.name).join(', ')}</div>
)
} catch (error) {
return (
<div>ERROR: {error.message}</div>
)
}
}
The activeIds
come from the URL, so we don't need a local state. Instead we want to update the URL in response to check/uncheck actions. Here I am using history.push
.
function TabNavigation({ activeIds }) {
const history = useHistory();
const handleChange = (id) => (e) => {
const newIds = e.target.checked
? // add id on check
[...activeIds, id]
: // remove id on uncheck
activeIds.filter((active) => active.toLowerCase() !== id.toLowerCase());
// push new ids to history
history.push("/predict/" + idsToSlug(newIds));
};
return (
<div>
{TABS.map((tab) => {
const isActive = activeIds
.map((id) => id.toLowerCase())
.includes(tab.id.toLowerCase());
return (
<div key={tab.id} className={isActive ? "active" : ""}>
<input
type="checkbox"
checked={isActive}
onChange={handleChange(tab.id)}
/>
<span>{tab.name}</span>
</div>
);
})}
</div>
);
}
The PredictPageRouter
might render something like this:
function PredictPageRouter() {
// this will be a string
const { slug = "" } = useParams<{ slug?: string }>();
// break string into array
const activeIds = slugToIds(slug);
// can render here or pass off to some other component
return (
<div>
<TabNavigation activeIds={activeIds} />
<TabContent activeIds={activeIds} />
</div>
);
}
Upvotes: 1