How to manage multiple navigation items states? - javascript

I'm trying to implement a horizontal menu with antd components.
When clicking the nav items the submenu is not showing correctly.
Codesandbox demo.
const MenuList = [
{
name: "Navigation two - Submenu",
subMenuRoutes: [
{
name: "A- item1",
url: "/item1Url1"
},
{
name: "A - item2",
url: "/item1Url2"
}
]
},
{
name: "Navigation Three - Submenu",
subMenuRoutes: [
{
name: "B- item1",
url: "/item1Url1"
},
{
name: "B - item2",
url: "/item1Url2"
}
]
}
];
function TextAreaManager() {
const [showMenu, setShowMenu] = useState(false);
return (
<Tabs onTabClick={() => setShowMenu(prev => !prev)}>
{MenuList.map(item => {
return (
<TabPane
tab={
<>
<Icon type="setting" />
{item.name}
<Icon
type={showMenu ? "up" : "down"}
style={{ marginLeft: "10px" }}
/>
</>
}
>
{showMenu && (
<Menu>
{item.subMenuRoutes.map(childItem => {
return (
<Menu.Item key={childItem.url}>{childItem.name}</Menu.Item>
);
})}
</Menu>
)}
</TabPane>
);
})}
</Tabs>
);

There are a few issues that need to be handled:
Assign unique key for every array item in order to render components correctly.
menuList.map(item => <TabPane key={item.name}></TabPane>);
You need to manage every menu's state in order to show menus correctly with the corresponding icon showMenuManager[item.name]:
<Tabs
onTabClick={e =>
setShowMenuManager(prev => {
const newState = { ...initMenuState, [e]: !prev[e] };
console.log(newState);
return newState;
})
}
/>;
const initMenuState = {
"Navigation two - Submenu": false,
"Navigation Three - Submenu": false
};
function TopMenuManager() {
const [showMenuManager, setShowMenuManager] = useState(initMenuState);
return (
<Tabs ... >
{menuList.map(item => (
<TabPane
key={item.name}
tab={
<>
...
<Icon
type={showMenuManager[item.name] ? "up" : "down"}
/>
</>
}
>
{showMenuManager[item.name] && ...}
</TabPane>
))}
</Tabs>
);
}
Check the final example and forked sandbox:

Related

Set active tabpanel mui from router nextjs

I'm trying to set active tab using router pid
Here is how
function dashboard({ tabId }) {
const classes = useStyles();
const [value, setValue] = React.useState("");
useEffect(() => {
console.log(tabId)
if (tabId) {
setValue(tabId);
}
}, [])
.......
}
Each of my TabPanel have a custom value how is the same I'm trying to pass as router parameter (pid)
The pid is get by getInitialProps
dashboard.getInitialProps = async ({ query }) => {
if (!query.pid) {
return { tabId: 0 }
}
return { tabId: query.pid }
}
So the issue is, the tab is set as active but the content of the tab is not show ... Only the tab is set to active...
Tabs/TabPanel
<Tabs value={value}
onChange={handleChange}
initialSelectedIndex={1}
variant="fullWidth"
aria-label="simple tabs example"
TabIndicatorProps={{
style: {
display:'none',
}
}}
>
<Tab label="ACCUEIL" value="accueil" classes={{ root: classes.rootTab, selected: classes.selectedTab }} {...a11yProps(0)} />
<Tab label="GÉRER SA GAMME" value="ma-gamme" classes={{ root: classes.rootTab, selected: classes.selectedTab }} {...a11yProps(1)} />
.........
</Tabs>
</AppBar >
<TabPanel value="accueil" index={0}>
</TabPanel>
<TabPanel value="ma-gamme" index={1}>
</TabPanel>
......
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Container>
<Box p={3}>
{children}
</Box>
</Container>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
Any help would be appreciate :)
Fixed by juliomalves
I was using string value instead of value={index}
Fixed by juliomalves
I was using string value instead of value={index}

I am having issues displaying List in React JS using material UI

I am trying to display list using react Js. There are 3 grid, what I want is to have a list of cities first and when i click on it, I will get the user that have the same city and then when I click on users, I should get their details. There is an unassigned list item too which should show incase a user has no city name
...
import React from "react";
export default class App extends React.Component
{
constructor()
{
super();
this.state = {
city: {
id: [1, 2, 3],
name: ["a", "b", "c"]
},
user: {
id: [1, 2, 3, 4, 5],
name: ["q", "w", "e", "f", "g"],
cityId: [2, 1, 3]
},
detail: {
userId: [3, 2, 1, 4, 5],
address: ["usa", "china", "india", "UK", "Japan"]
},
marker: null
};
}
change(id) {
this.setState({ marker: id });
}
render() {
console.log(this.state);
return (
<Grid container>
{/* //city list */}
<Grid>
<List>
{this.state.city.id.map((id, index) => {
return (
<ListItem
key={id}
button
onClick={() => {
this.change(id);
}}
>
<ListItemText primary={this.state.city.name[index]} />
</ListItem>
);
})}
<ListItem
key="unassigned"
button
onClick={() => {
this.change("unassigned");
}}
>
<ListItemText primary={"unassigned"} key={"unassigned"} />
</ListItem>
</List>
</Grid>
{/* //list of users where they live */}
<Grid>
{this.state.city.id.map((cityId, index) => {
switch (this.state.marker) {
case cityId: {
return (
<List>
{this.state.user.id.map((userId, index) => {
if (cityId === this.state.user.cityId[index]) {
return (
<List>
<ListItem
key={userId}
button
onClick={() => {
this.change(String(userId));
}}
>
<ListItemText
primary={this.state.user.name[index]}
/>
</ListItem>
</List>
);
}
})}
</List>
);
}
case "unassigned": {
return (
<List>
{this.state.user.id.map((userId, index) => {
if (cityId !== this.state.user.cityId[index]) {
return (
<List>
<ListItem
key={userId}
button
onClick={() => {
this.change(String(userId));
}}
>
<ListItemText
primary={this.state.user.name[index]}
/>
</ListItem>
</List>
);
}
})}
</List>
);
}
default:
return null;
}
})}
</Grid>
{/* //details of users*/}
<Grid>
{this.state.detail.userId.map((userId, index) => {
switch (this.state.marker) {
case String(userId): {
return (
<List>
<ListItem
key={userId}
button
onClick={() => {
this.change(userId);
}}
>
<ListItemText
primary={this.state.detail.address[index]}
/>
</ListItem>
</List>
);
}
default:
return null;
}
})}
</Grid>
</Grid>
);
}
}
...
I have tried implementing this on code sandbox, here is the link
https://codesandbox.io/s/focused-shamir-y85v9?file=/src/App.js:0-4390
You need to track the selected user and city independently.
Add a second tracker to your state and save the selected user there.
Here is your updated, working sandbox.

Conditional rendering does not display my data properly

I have a component to display data on a material-ui table called UserRow.
This component is used on another component called users.
But in order to display the data properly in each field the only way I found was to create a conditional rendering, so i could just render the data that i wanted in each field, otherwise it would be duplicated.
Is there a way to just call once the <UserRow/> component and get the same results as i get in the image bellow?
UserRow:
export default function UserRow( props, {name, details}) {
const style = styles();
function UserName(props) {
const showUserRow = props.showUserRow;
if (showUserRow) {
return <ListItem>
<ListItemIcon >
<PeopleIcon className={style.iconColor}/>
</ListItemIcon>
<ListItemText className={style.text}>{props.userRow}</ListItemText>
</ListItem>
}
return<div></div>;
}
function IconDetails(props) {
const showDetailsRow = props.showDetailsRow;
if (showDetailsRow) {
return <Link to={`/users/${props.detailsRow}`}>
<ListItemIcon >
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>;
}
return<div></div>;
}
return (
<List>
<ListItem>
<UserName userRow={props.name} showUserRow={props.showUserRow}/>
<IconDetails detailsRow={props.details} showDetailsRow={props.showDetailsRow}/>
</ListItem>
</List>
)
}
users:
export default function User({ data }) {
const style = styles();
const userList = data.map((row) => {
return { name: row, details: row };
});
const [state] = React.useState({
users: [
...userList,
]
});
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<UserRow showUserRow={true} showDetailsRow={false} name={rowData.name} />
)
},
{
title: "Details",
field: "details",
render: rowData => (
<UserRow showUserRow={false} showDetailsRow={true} details={rowData.details} />
)
},
]}
data={state.users}
options={{
search: true
}}
/>
</div>
)
}
What i had before:
UserRow:
export default function UserRow( props, {name, details}) {
const style = styles();
return (
<List>
<ListItem>
<ListItemIcon >
<PeopleIcon className={style.color}/>
</ListItemIcon>
<ListItemText className={style.text}>{name}</ListItemText>
<Link to={`/users/${details}`}>
<ListItemIcon >
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>
</ListItem>
</List>
)
}
users:
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<UserRow name={rowData.name} details={rowData.details} />
)
},
{
title: "Details",
},
]}
data={state.users}
options={{
search: true
}}
/>
</div>
)
}
The problem here, in the previous solution, is that if we create a title Details, the material-ui table creates a div for the details and I can't place my icon there, and this would be a problem if i had more data and need to place the data in the respective position.
What i was trying to achieve with the previous solution was to cut down some code, because if i have many fields i will repeat too much code.
Link that might be useful: https://material-table.com/#/docs/get-started

multiple iteration is done for creating the generic filters

I am trying to develop a generic filter component which can have many fields to filter on like color,
size, price range etc and each field might have different types of elements like color may have
checkboxes, radio button and price range might have input element, dropdown etc. To support such varied
cases, I tried to go with this pattern but here I have to iterate the same things multiple times.
I am not sure of this data structure. If anyone has suggestion please help me to improve this code but
the main problem here is "multiple iteration". How can i improve this code?
const filterParams = {
field: {
id : 1, label : 'Field', content: <FieldFilter />
},
employee: {
id : 1, label : 'Employee', content: <Employee />
}
}
<Filter filterParams={filterParams} activeFilterParam="field" />
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
return (
<>
<Button secondary icon={filter} onClick={() => setShow(!show)}>Filter</Button>
{show && (
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List
render={() => {
return (
Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
)
}))
}} />
<Tabs.Panels>
{Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
)
})}
</Tabs.Panels>
</Tabs>
</Card.Content>
<Card.Footer>
<Button>
<Button.Content style={{ marginRight: 10 }}>Save</Button.Content>
<Button.Content secondary onClick={()=>setShow(!show)}>Cancel</Button.Content>
</Button>
</Card.Footer>
</Card>
)}
</>
)
}
If you're not liking the multiple calls to Object.keys(filterParams).map, you could move the loop to the top of the component function. Something like the below might work:
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
const {tabs, panels} = Object.keys(filterParams)
.reduce((acc, filterParam) => {
acc.tabs.push(
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
);
acc.panels.push(
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
);
return acc;
}, { tabs: [], panels: [] });
return (
...
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List render={() => tabs} />
<Tabs.Panels>
{panels}
</Tabs.Panels>
</Tabs>
...
</Card>
...
)
}
Note that I haven't run this - it likely won't be quite right, but should give the general idea...

How to make dynamic state for multiple Collapse

I'm need make Collapse menu from array data,
now i'm click any menu but expand all sub menu
I think my problem is i'm can,t set unique state "open" for Main menu
I don't want to be assigned "state"
To support additional data in the future that may have 3 or 4
I'm use React.js, material-ui
please help me
thank you very much
const myData = [
{
id: '1',
nameHeader: 'Header1',
subMenu: [{ id: '1', name: 'subMenu1' }, { id: '2', name: 'subMenu2' }]
},
{
id: '2',
nameHeader: 'Header2',
subMenu: [{ id: '1', name: 'subMenu1' }, { id: '2', name: 'subMenu2' }]
}
]
class Myclass extends Component {
state = { open: false }
handleClick = () => {
this.setState(state => ({ open: !state.open }))
}
render() {
const { open } = this.state
return (
<div style={{ marginRight: '15px' }}>
<List component="nav">
{myData.map(each => (
<React.Fragment key={each.id}>
<ListItem button onClick={this.handleClick}>
<ListItemText inset primary={each.nameHeader} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Divider />
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{each.subMenu.map(subData => (
<ListItem key={subData.id} button>
<ListItemText inset primary={subData.name} />
</ListItem>
))}
</List>
</Collapse>
</React.Fragment>
))}
</List>
</div>
)
}
}
export default Myclass
I think my problem is i'm can,t set unique state "open" for Main menu
Somehow, yes. You have two different (sub) menus you like to collapse / expand without having an effect on the other menu. This in mind, you need to create a possibility to save the current 'open' state for each of the menus separately.
You already have a unique id for your different menus, you can use them in order to achieve your goal. One way would be to extend your state with the related settings for your menus:
state = { settings: [{ id: "1", open: false }, { id: "2", open: false }] };
This allows you to have the information about the collapsed status of each of your menus.
According to that you need to extend your handleClick function a bit in order to only change the state of the menu item you clicked:
handleClick = id => {
this.setState(state => ({
...state,
settings: state.settings.map(item =>
item.id === id ? { ...item, open: !item.open } : item
)
}));
};
And in your render function you need to make sure that you pass the right id of the menu item you clicked to your handleClick function and that you select the right open state.
<React.Fragment key={each.id}>
<ListItem button onClick={() => this.handleClick(each.id)}>
<ListItemText inset primary={each.nameHeader} />
settings.find(item => item.id === each.id).open
? "expanded"
: "collapsed"}
</ListItem>
<Divider />
<Collapse
in={settings.find(item => item.id === each.id).open}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{each.subMenu.map(subData => (
<ListItem key={subData.id} button>
<ListItemText inset primary={subData.name} />
</ListItem>
))}
</List>
</Collapse>
</React.Fragment>
See it working here: https://codesandbox.io/s/6xjz837j9z

Categories

Resources