Material UI Tabs with routes - reactjs

So I'm using Material UI Tabs, and I'm trying when a user taps on tab it redirects him to a tab with a link but it would be still in the same main page
Example of what i'm looking for please notice the link changes as the tabs changes:
Material UI VerticalTabs
import * as React from 'react';
import PropTypes from 'prop-types';
import Tabs from '#mui/material/Tabs';
import Tab from '#mui/material/Tab';
import Typography from '#mui/material/Typography';
import Box from '#mui/material/Box';
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`vertical-tabpanel-${index}`}
aria-labelledby={`vertical-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
};
function a11yProps(index) {
return {
id: `vertical-tab-${index}`,
'aria-controls': `vertical-tabpanel-${index}`,
};
}
export default function VerticalTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Box
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 224 }}
>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
</Box>
);
}
I know that I have to use react-router-dom but I didn't know how to implement, any help would be appreciated.

Create a route component or destructive the routes in App.js. Just install react-router-dom and use BrowserRouter as Route as the Parent tag. Then add routes and structure your routes for your pages there.
import {BrowserRouter as Router, Routes, Route} from 'react-route-dom'
<Router>
<Routes>
<Route path='/pageName' element={<pageName/>}>
<Route path='/pageName2' element={<pageName2/>}>
</Routes>
</Router>
Also refer react router v6 docs. This playlist would helpful to using different routing mechanism. In your case you need to watch the tutorial 8 to do the nested routes. If you're a person who familiar with v5 and want to know the changes in v6, then refer this video also.

Related

Tabs that load before the internal tabs causes an error "The value provided to the Tabs component is invalid"

I use a tabs element of material-UI, but the tabs itself I receive from props.
the first tab with the value 0, I want to be chosen.
Here my code:
import React, { useContext } from 'react';
import { ChannelContext } from '../context/channelContext'
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
export default function Channels(props) {
let channelsList = []
props.channels.forEach((elem,i) => {
channelsList.push( <Tab key={i} label={elem.channel_name} value={elem.server_id-1} />)
})
const [, setchannelBanner] = useContext(ChannelContext)
const [channelId, setChannelId] = React.useState(0);
const handleChange = (event, newValue) => {
setChannelId(newValue)
setchannelBanner(props.channels[newValue].bg_image);
};
return (
<AppBar position="static" color="inherit" elevation={0}>
<Tabs
value={channelId}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs" className="tabs">
{channelsList}
</Tabs>
</AppBar>
);
}
I receive this error on the browser console:
"Material-UI: The value provided to the Tabs component is invalid.
None of the Tabs' children match with `0`.
You can provide one of the following values: NaN."
if i change the React.useState(0) to React.useState(false) or React.useState(NaN), the error disappear but the first tab not choosen.
I understand that it's happening because the main Tabs element loads before the internal tabs array.
How do you suggest resolving it?
One way of solving it would be to use useEffect and change the state from NaN to 0 when the main tabs are loaded.
It would be better if you can share a link to code sandbox
I found the answer is to use useEffect on props and then inside the useEffect check if the first element/channel exists, only then make the setChannelId(0).
import React, { useContext, useEffect } from 'react';
import { ChannelContext } from '../context/channelContext'
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
export default function Channels(props) {
const [, setchannelBanner] = useContext(ChannelContext)
const [channelId, setChannelId] = React.useState(false);
// setChannelId after tabs are loaded
useEffect(() => {
if( props.channels[0].server_id >= 0 ) setChannelId(0)
}, [props]);
const handleChange = (event, newValue) => {
setChannelId(newValue)
setchannelBanner(props.channels[newValue].bg_image);
};
return (
<AppBar position="static" color="inherit" elevation={0}>
<Tabs
value={channelId}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs" className="tabs">
{props.channels.map((elem,i) => (
<Tab key={i} label={elem.channel_name} value={elem.server_id-1}/>
))}
</Tabs>
</AppBar>
);
}

Material-UI and React IE11 compatibility

I'm trying to implement material-ui in my React application. Unfortunately we still have to support IE, but anything material-ui I try to add makes the app not display, and with loads of undefined errors in the console. For example, I'm trying to add the Tabs component code directly from material-ui's site (using v4.8.3):
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`nav-tabpanel-${index}`}
aria-labelledby={`nav-tab-${index}`}
{...other}
>
{value === index && <Box p={3}>{children}</Box>}
</Typography>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `nav-tab-${index}`,
'aria-controls': `nav-tabpanel-${index}`,
};
}
function LinkTab(props) {
return (
<Tab
component="a"
onClick={event => {
event.preventDefault();
}}
{...props}
/>
);
}
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
},
}));
export default function Header() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs
variant="fullWidth"
value={value}
onChange={handleChange}
aria-label="nav tabs example"
>
<LinkTab label="Page One" href="/drafts" {...a11yProps(0)} />
<LinkTab label="Page Two" href="/trash" {...a11yProps(1)} />
<LinkTab label="Page Three" href="/spam" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
Page One
</TabPanel>
<TabPanel value={value} index={1}>
Page Two
</TabPanel>
<TabPanel value={value} index={2}>
Page Three
</TabPanel>
</div>
);
}
but all I get in IE is many of these:
Is there something I am missing here? Any polyfills I need to add? Chrome displays just fine with no errors. Any help is much appreciated.
From the Supported Platform of Material-UI, it says it also supports Internet Explorer 11. You don't need to provide any JavaScript polyfill as it manages unsupported browser features internally and in isolation.
I made a demo using Tab component and the code you providing, it works well in IE 11. Maybe the error occurs in other codes and you could check again where the error points to. Besides, have you had the right package.json? I use package.json like below:
{
"title": "Material demo",
"devDependencies": {
"react-scripts": "latest"
},
"description": "https://github.com/mui-org/material-ui/blob/master/docs/src/pages/components/tabs/SimpleTabs.js",
"dependencies": {
"#material-ui/core": "latest",
"#types/react": "^16.8.6",
"prop-types": "latest",
"react": "latest",
"react-dom": "latest"
}
}
You could refer to my online demo. If the issue still persists, you could provide a reproducible demo using online code editor like StackBiltz. I think that will help us have a better understanding of the issue and see how to help.

Set Antd's Menu defaultSelectedKeys value using React and Redux

I'm using antd for building a react application. I'm using antd's Layout to design the layout. Also, I'm using react-router for routing and redux for state management.
In antd's Menu I've set the defaultSelectedKeys to a blank array so that when page loads I don't want to show the active menu item. Instead, When the user accesses a certain page{Component) using application URL in the browser, then that page's menu item should be active.
For example, by default defaultSelectedKeys array will be blank. So no menu item will be active initially. When the application loads and react-router routes to the home component then the home menu item should be active.
For this, I'm using useEffect hook to dispatch an action with the menu key when a particular component is mounted.
Example Home Component:
import React, { Fragment, useEffect } from "react";
import { useDispatch } from "react-redux";
import { CURRENT_COMPONENT } from "./../reducers/types";
export default function HomeComponent() {
const dispatch = useDispatch();
useEffect(() => {
dispatch({
type: CURRENT_COMPONENT,
payload: { component: "Landing", sideBarMenuKey: "1" }
});
}, [dispatch]);
return (
<Fragment>
<h1>This is a home componet </h1>
<br />
...
<br />
<br />
...
<br />
<br />
...
<br />
</Fragment>
);
}
Up here in the useEffect I'm dispatching an action to the reducer with sideBarMenuKey: "1" and it is dispatching perfectly when this component mounts and able to receive the state change in Layout component as well using the react-redux useSelector hook.
Example Layout Component:
import React, { Fragment } from "react";
import { useSelector } from "react-redux";
import { Layout, Menu, Breadcrumb } from "antd";
import { Switch, Route } from "react-router-dom";
import HomeComponent from "./Home";
import AboutComponent from "./About";
const { Header, Content, Footer } = Layout;
export default function LayoutComponent() {
const sideBarMenuKey = useSelector(
state => state.currentComponetReducer.sideBarMenuItemKey
);
console.log(sideBarMenuKey);
return (
<Fragment>
<Layout className="layout">
<Header>
<div className="logo" />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={[sideBarMenuKey]}
style={{ lineHeight: "64px" }}
>
<Menu.Item key="1">Home</Menu.Item>
<Menu.Item key="2">About</Menu.Item>
</Menu>
</Header>
<Content style={{ padding: "0 50px" }}>
<Breadcrumb style={{ margin: "16px 0" }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<div style={{ background: "#fff", padding: 24, minHeight: 280 }}>
<Switch>
<Route exact path="/" component={HomeComponent} />
<Route exact path="/about" component={AboutComponent} />
</Switch>
</div>
</Content>
<Footer style={{ textAlign: "center" }}>
Ant Design ©2018 Created by Ant UED
</Footer>
</Layout>
</Fragment>
);
}
Even though I'm receiving the state change in the Layout Component and when I use sideBarMenuKey in antd's Menu prop as defaultSelectedKeys={[sideBarMenuKey]} the menu item doesn't get active state.
Sample Example:
I've created a sample codesandbox example. The link is below
https://codesandbox.io/s/styled-antd-react-starter-ytxko
I've created a Layout, Home and About components. Initially, when you log in it will show Home component. If you want to render the About component, then change the browser URL in the codesandbox to https://url/about. Both in Home and About component I'm dispatching an action with sideBarMenuKey and the state is also getting updated. But the menu item is not getting active.
Changing defaultSelectedKeys to selectedKeys should make it work:
<Menu
theme="dark"
mode="horizontal"
selectedKeys={[sideBarMenuKey]}
style={{ lineHeight: "64px" }}
>
<Menu.Item key="1">Home</Menu.Item>
<Menu.Item key="2">About</Menu.Item>
</Menu>
Putting NavLink inside the Menu.Item will allow you to switch from a nav tab to another.
<Menu
theme="dark"
mode="horizontal"
selectedKeys={[sideBarMenuKey]}
style={{ lineHeight: "64px" }}
>
<Menu.Item key="1">
<NavLink to="/">nav 1</NavLink>
</Menu.Item>
<Menu.Item key="2">
<NavLink to="/about">nav 1</NavLink>
</Menu.Item>
<Menu.Item key="3">nav 3</Menu.Item>
</Menu>
You can see it in action here
const [sideBarMenuKey, setSideBarMenuKey] = useState(1);
<Menu theme="dark" mode="inline" defaultSelectedKeys={[sideBarMenuKey]}>
<Menu.Item key="1" icon={<BirthIcon style={{ paddingRight: '10px' }} />}
onClick={ () => {
setSideBarMenuKey(1)
setShowBirth(true);
}
} >
Birth
</Menu.Item>
<Menu.Item key="2"
icon={<DeathIcon style={{ paddingRight: '10px' }} />}
onClick={ () => {
setSideBarMenuKey(2)
setShowBirth(false)
} }>
Death
</Menu.Item>
</Menu>
Using hooks look on to this

Custom layout in SimpleForm component on react-admin

I want to create a custom two-column-grid layout on my react-admin project on Edit and Show pages. I want to display selectboxes and the imageupload area on the left column, and the text inputs on the right column by using only one <SimpleForm>.
Simply like this
If I use a div or a <Card> component under <SimpleForm> and <EditController> components, I receive an error.
Warning: React does not recognize the `basePath` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase `basepath` instead. If you
accidentally passed it from a parent component, remove it from the DOM
element.
Is there any way to create a layout without this error?
I solved it with creating another component with using divs, <Grid/> etc, and used that component in <SimpleForm> component.
import {withStyles} from '#material-ui/core/styles';
import React from 'react';
import {
EditController,
SimpleForm,
TextInput,
SelectInput,
Title,
} from 'react-admin';
import Grid from '#material-ui/core/Grid';
import Card from '#material-ui/core/Card';
import Poster from "../customField/Poster";
import {EditToolbar} from '../toolbar/CustomToolbar'
import {EditActions} from '../toolbar/CustomActions'
const editStyles = {
root: {display: 'flex', alignItems: 'flex-start', width: '100%'},
form: {flexGrow: 9},
};
class CardEdit extends React.Component {
constructor(props) {
super(props);
this.state = {
refresh: false
};
}
render() {
const FormDiv = withStyles(editStyles)(({children, classes, ...props}) => {
return (
<div className={classes.root}>
<div className={classes.form}>
<Grid container spacing={24}>
<Grid item xs={6}>
<TextInput source="name" fullWidth />
</Grid>
<Grid item xs={6}>
<TextInput source="card_id" fullWidth />
</Grid>
</Grid>
</div>
</div>
)
}
)
return (
<EditController {...this.props}>
{({resource, record, redirect, save, basePath, version}) => {
return (
<div>
<Title defaultTitle="sample"/>
<Card>
<div style={{ margin: '20px 20px 0 0' }}>
<EditActions
basePath={basePath}
resource={resource}
data={record}
hasShow
hasList
/>
</div>
{record && (
<SimpleForm
basePath={basePath}
redirect={redirect}
resource={resource}
record={record}
save={save}
version={version}
toolbar={<EditToolbar/>}
>
<FormDiv record={record} />
</SimpleForm>
)}
</Card>
</div>
)
}}
</EditController>
)
}
}
export default withStyles(editStyles)(CardEdit);
Actually, this could be done a little bit easier in case you don't need any custom styles and what not.
In order to get rid of the basePath error, just sanitize the props passed to the Material UI Grid Component:
const SanitizedGrid = ({basePath, ...props}) => {
return (
<Grid {...props} />
);
};
Then use it in place of a normal Grid:
export default props => (
<SimpleForm {...props}>
<SanitizedGrid container spacing={16}>
<Grid item xs>
<TextInput source="name" />
</Grid>
</SanitizedGrid>
</SimpleForm>
);
As another way, I've just worked out (thanks to Alexander's answer) a nice generic way to add any custom HTML content to a react-admin form:
import React, { Fragment } from 'react';
import { SimpleForm } from 'react-admin';
const CustomContent = ({ basePath, record, resource, children }) => (
<Fragment>
{children}
</Fragment>
);
export const MyForm = (props) => (
<SimpleForm>
<CustomContent>
<h3>Custom Content</h3>
<p>I can now add standard HTML to my react admin forms!</p>
</customContent>
</SimpleForm>
);
You get the basePath prop (which you probably don't want), but the record and resource props might be useful to your custom content (if you switch the code to use a render prop)

Material-ui svg-icons inside another element doesnt align to the center

I'm using the material ui library in my react project, and I have come across a strange issue, when I try to use svg icons inside a button-icon, the icom doesn't align to the center.
for example:
<ListItem key={product.id}
primaryText={product.title}
leftAvatar={<Avatar src={product.img}/>}
rightIcon={<IconButton><RemoveIcon/></IconButton>}/>
for this code I will get the following result:
And for this code:
<ListItem key={product.id}
primaryText={product.title}
leftAvatar={<Avatar src={product.img}/>}
rightIcon={<RemoveIcon/>}/>
I will get the following result :
My question is, how do i get to the result of my second example, but that the icon will we inside another element?
This is kind of late but I recently had the same issue and solved it by wrapping the IconButton component in a custom component and extending the css. You may have to change some other CSS to make it align perfectly but this worked for my use case.
import React, { PropTypes, Component } from 'react';
import IconButton from 'material-ui/IconButton';
const CustomIconButton = (props) => {
const { style } = props;
const additionalStyles = {
marginTop: '0'
};
return(
<IconButton {...props } style={{ ...style, ...additionalStyles }} iconStyle={{ fontSize: '20px' }}/>
);
};
CustomIconButton.PropTypes = {
// listed all the props that IconButton requires (check docs)
};
export default PPIconButton;
This is what a simplified usage of this custom IconButton looks like:
const deleteIconButton = (deleteFunc) => {
return <CustomIconButton
touch={true}
tooltip="Delete"
tooltipPosition="top-right"
onTouchTap={deleteFeed}
iconClassName="fa fa-trash"
/>;
};
class MyList extends Component {
render() {
return (
<div>
<List>
<ListItem value={ i } primaryText="My List Item" rightIcon={ deleteIconButton(() => this.props.deleteFeed(i) } />
) }
</List>
</div>
);
}
}
Passing the styles down to the inner element worked for me:
return <SvgIcon style={this.props.style} />
check this code, working fine for me
import React from 'react';
import List from 'material-ui/List';
import ListItem from 'material-ui/List/ListItem';
import Delete from 'material-ui/svg-icons/action/delete';
const MenuExampleIcons = () => (
<div>
<List style={{width:"300px"}}>
<ListItem primaryText="New Config" leftIcon={<Delete />} />
<ListItem primaryText="New Config" rightIcon={<Delete />} />
</List>
</div>
);
export default MenuExampleIcons;

Resources