React Material-UI drawer not woring when calling child component function from parent - javascript

I have a sample code for App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Child from './child';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<Child ref={instance => { this.child = instance; }}/>
<button onClick={() => { this.child.onAlert(); }}>Click</button>
</div>
);
}
}
export default App;
And child component like
import React, { Component } from 'react';
class Child extends Component {
state = { }
onAlert =()=>
alert("hey");
render() {
return (
<div> IM kid</div>
);
}
}
export default Child;
here when I click on button in App.js I am able to get the expected output
i.e., i am able to call the child function onAlert()
I am using the same scenario in material-ui and react where I need to trigger Drawer Component from Toolbar Component
and my code is like Titlebar.js below code is my parent component here
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Toolbar from 'material-ui/Toolbar'
import AppBar from 'material-ui/AppBar';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import Child from './child';
const styleSheet = createStyleSheet('Titlebar', () => ({
root: {
position: 'relative',
width: '100%',
},
appBar: {
position: 'relative',
},
flex: {
flex: 1,
}
}));
class TitleBar extends Component {
render() {
const classes = this.props.classes;
return (
<div className={classes.root}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton contrast onClick={() => { this.child.handleLeftOpen(); }}>
<MenuIcon />
</IconButton>
<Typography type="title" colorInherit className={classes.flex}>Productivity Dashboard</Typography>
</Toolbar>
</AppBar>
<Child ref={instance => { this.child = instance; }}/>
</div>
)
}
}
TitleBar.PropTypes={
classes:PropTypes.object.isRequired,
}
export default withStyles(styleSheet)(TitleBar);
and my child component code Child.js is below
import React, { Component } from 'react';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import InboxIcon from 'material-ui-icons/Inbox';
import DraftsIcon from 'material-ui-icons/Drafts';
import StarIcon from 'material-ui-icons/Star';
import SendIcon from 'material-ui-icons/Send';
import MailIcon from 'material-ui-icons/Mail';
import DeleteIcon from 'material-ui-icons/Delete';
import ReportIcon from 'material-ui-icons/Report';
const styleSheet = createStyleSheet('Child', {
list: {
width: 250,
flex: 'initial',
},
listFull: {
width: 'auto',
flex: 'initial',
},
});
class Child extends Component {
state = {
open: {
top: false,
left: false,
bottom: false,
right: false,
},
}
handleLeftOpen = () =>{
console.log("im here")
this.toggleDrawer('left', true);
}
handleLeftClose = () => this.toggleDrawer('left', false);
toggleDrawer = (side, open) => {
const drawerState = {};
drawerState[side] = open;
this.setState({ open: drawerState });
};
render() {
const classes=this.props.classes;
return (
<Drawer
open={this.state.open.left}
onRequestClose={this.handleLeftClose}
onClick={this.handleLeftClose}
>
<List className={classes.list} disablePadding>
<ListItem button>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItem>
<ListItem button>
<ListItemIcon>
<StarIcon />
</ListItemIcon>
<ListItemText primary="Starred" />
</ListItem>
<ListItem button>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText primary="Send mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItem>
</List>
<Divider />
<List className={classes.list} disablePadding>
<ListItem button>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary="All mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DeleteIcon />
</ListItemIcon>
<ListItemText primary="Trash" />
</ListItem>
<ListItem button>
<ListItemIcon>
<ReportIcon />
</ListItemIcon>
<ListItemText primary="Spam" />
</ListItem>
</List>
</Drawer>
);
}
}
export default withStyles(styleSheet)(Child);
Here I am calling handleLeftOpen() function from my parent when I click on the IconButton in the Tiltlbar Component I am not getting the expected output. I am getting error like below in my console
Uncaught TypeError: Cannot read property 'handleLeftOpen' of null
at onClick (http://localhost:3000/static/js/bundle.js:90993:50)
at Object.ReactErrorUtils.invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:17236:17)
at executeDispatch (http://localhost:3000/static/js/bundle.js:17019:22)
at Object.executeDispatchesInOrder (http://localhost:3000/static/js/bundle.js:17042:6)
at executeDispatchesAndRelease (http://localhost:3000/static/js/bundle.js:16430:23)
at executeDispatchesAndReleaseTopLevel (http://localhost:3000/static/js/bundle.js:16441:11)
at Array.forEach (native)
at forEachAccumulated (http://localhost:3000/static/js/bundle.js:17339:10)
at Object.processEventQueue (http://localhost:3000/static/js/bundle.js:16644:8)
at runEventQueueInBatch (http://localhost:3000/static/js/bundle.js:24266:19)
please check the code and let me know if anything need to be changed

The difference here is that in your first example you export:
export default Child;
In the second example you export:
export default withStyles(styleSheet)(Child);
This returns a decorated component, so the ref is put on this decorated component and not your Child component. To solve this issue the decorated component accepts a property called innerRef so you can pass a ref to your own component. So to solve this you change:
<Child ref={instance => { this.child = instance; }}/>
to
<Child innerRef={instance => { this.child = instance; }}/>

Related

Opening a drawer component from my navigation bar component

I am using ChakraUI and react and I want to open my NavigationDrawer using a button on my NavigationBar. I need to call the useDisclosure from my drawer component in my bar compoment. How would I do that? I couldn't find online how to do this.
My NavigationDrawer: component
import {
Drawer,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
IconButton,
useDisclosure,
} from '#chakra-ui/react'
import { HamburgerIcon } from '#chakra-ui/icons'
import { React } from 'react'
function NavigationDrawer() {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<>
<IconButton icon={<HamburgerIcon />} aria-label='Open navigation drawer' variant='solid' onClick={onOpen}>
Open
</IconButton>
<Drawer placement='left' onClose={onClose} isOpen={isOpen}>
<DrawerContent>
<DrawerCloseButton aria-label='Open navigation drawer'></DrawerCloseButton>
</DrawerContent>
</Drawer>
</>
)
}
export default NavigationDrawer;
My NavigationBar component:
// src/components/navigation/NavigationBar.jsx
import { IconButton, useColorMode, Flex } from '#chakra-ui/react'
import { MoonIcon, SunIcon, HamburgerIcon } from '#chakra-ui/icons'
function NavigationBar() {
const { colorMode, toggleColorMode } = useColorMode()
return (
<>
<Flex
as="nav"
align="center"
justify="space-between"
wrap="wrap"
w="100%"
mb={8}
p={8}
bg={"black"}
>
<IconButton icon={<HamburgerIcon />} aria-label='Open navigation drawer' variant='solid'>
Open
</IconButton>
<IconButton aria-label='Toggle theme' onClick={toggleColorMode} icon={colorMode === 'light' ? <SunIcon /> : <MoonIcon />} />
</Flex>
</>
)
}
export default NavigationBar
How I combine my NavigationDrawer and NavigationBar:
// src/components/navigation/Navigation.jsx
import NavigationDrawer from './NavigationDrawer';
import NavigationBar from './NavigationBar';
function Navigation() {
return (
<>
<NavigationDrawer />
<NavigationBar />
</>
)
}
export default Navigation
One option would be to move the useDisclosure hook up one level and pass the props down to the drawer and navigation bar.
//Navigation.jsx
import { useDisclosure } from '#chakra-ui/react'
import NavigationDrawer from './NavigationDrawer'
import NavigationBar from './NavigationBar'
function Navigation() {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<>
<NavigationDrawer isOpen={isOpen} onClose={onClose} />
<NavigationBar onClick={onOpen} />
</>
)
}
export default Navigation
//NavigationDrawer.jsx
import { Drawer, DrawerOverlay, DrawerContent, DrawerCloseButton } from '#chakra-ui/react'
import { React } from 'react'
function NavigationDrawer({ isOpen, onClose }) {
return (
<Drawer placement="left" onClose={onClose} isOpen={isOpen}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton aria-label="Open navigation drawer"></DrawerCloseButton>
</DrawerContent>
</Drawer>
)
}
export default NavigationDrawer
//NavigationBar.jsx
import { IconButton, useColorMode, Flex } from '#chakra-ui/react'
import { MoonIcon, SunIcon, HamburgerIcon } from '#chakra-ui/icons'
function NavigationBar({ onClick }) {
const { colorMode, toggleColorMode } = useColorMode()
return (
<Flex as="nav" align="center" justify="space-between" wrap="wrap" w="100%" mb={8} p={8} bg={'black'}>
<IconButton
icon={<HamburgerIcon />}
aria-label="Open navigation drawer"
variant="solid"
onClick={onClick}
>
Open
</IconButton>
<IconButton
aria-label="Toggle theme"
onClick={toggleColorMode}
icon={colorMode === 'light' ? <SunIcon /> : <MoonIcon />}
/>
</Flex>
)
}
export default NavigationBar

Not able to call onChange function of a TextInput in Redux, React JS

I am working on an app with React and Redux and displaying some data from API in TextInput control. But now I am not able to edit the data in the TextInput. Following is my complete code of the class:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Article from "grommet/components/Article";
import Box from "grommet/components/Box";
import Button from "grommet/components/Button";
import Header from "grommet/components/Header";
import Heading from "grommet/components/Heading";
import Section from "grommet/components/Section";
import AdminMenu from "../../components/Nav/Admin";
import NavControl from "../../components/Nav/Control";
import { getMessage } from "grommet/utils/Intl";
import Notices from "../../components/Notices";
import CheckBox from "grommet/components/CheckBox";
import TextInput from "grommet/components/TextInput";
import { pageLoaded } from "../utils";
import {
recognitionSettingsLoaded,
recognitionSettingsSaved,
} from "../../actions/settings-recognition";
import dashboard from "../../reducers/dashboard";
class Settings extends Component {
constructor(props) {
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount() {
const { dispatch, settingRecognition } = this.props;
console.log(this.props.state);
console.log(dashboard);
dispatch(recognitionSettingsLoaded("2"));
pageLoaded("Configuration");
}
onSave() {
const { survey, dispatch } = this.props;
dispatch(
recognitionSettingsSaved(
this.props.settingRecognition.days,
this.props.settingRecognition.active
)
);
}
handleDaysChange(e) {
const days = e.target.value;
settingRecognition.days = days;
}
handleActiveChange(e) {
const active = e.target.value;
settingRecognition.active = active;
}
render() {
const { dispatch, settingRecognition } = this.props;
console.log("render method");
console.log(settingRecognition);
const { intl } = this.context;
return (
<Article primary={true}>
<Header
direction="row"
justify="between"
size="large"
pad={{ horizontal: "medium", between: "small" }}
>
<NavControl name={getMessage(intl, "Configuration")} />
<AdminMenu />
</Header>
<Box pad={{ horizontal: "medium", vertical: "medium" }}>
<Heading tag="h4" margin="none">
{getMessage(intl, "RecognitionLifetime")}
</Heading>
<Heading tag="h5" margin="none">
{getMessage(intl, "DefineIsRecognitionTemporary")}
</Heading>
<Box direction="row">
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "NewUserActive")}
</Heading>
</Box>
<Heading tag="h3" margin="none">
{getMessage(intl, "HideAfter")}
</Heading>
<Box direction="row">
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "Days")}
</Heading>
</Box>
<Button
path="/recognition-settings"
label={getMessage(intl, "NewUserSave")}
primary={true}
onClick={() => {
this.onSave();
}}
/>
</Box>
<Notices />
</Article>
);
}
}
Settings.propTypes = {
dispatch: PropTypes.func.isRequired,
settingRecognition: PropTypes.object.isRequired,
};
Settings.contextTypes = {
intl: PropTypes.object,
};
const mapStateToProps = (state) => ({
settingRecognition: state.settingRecognition,
});
export default connect(mapStateToProps)(Settings);
I have created handleDaysChange function which should run on the text change of TextInput control. I have done similar thing for the checkbox and that works fine but I am not able to get it working for the TextInput.
You are not binding your change events.
Try this....
class Settings extends Component {
constructor(props){
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount(){
....
}
......
}
and change this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={(e) => this.handleActiveChange(e)}
/>
To this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>
same for text input
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>
You need to set up two-way-binding so that the content of the textInput reflects the prop that you set in your onChange function. Try giving your textInput a property of value={this.settingRecognition.days}

Switch function in react component not working as logic would suggest

So my react code does not execute as logic would suggest:
I am trying to create a page that resembles an email app.
I have components in React which I import into my message(main) component (e.g. Inbox component, send mail component etc). I have a useState, which is set to 'inbox' as default and a switch statement which checks which value is in the state and returns the component matching that case. I also have a onclick function which changes the state value based on which email tab you want to view (e.g. Inbox or trash etc). When the state changes the component should run the switch statement in my page render and display the correct component in the main component, which is does most of the time.
My problem comes when I keep switching between the tabs which have components it at some point goes to the default of the switch statement which does not make sense because either of the values should have passed.
Please help
import React, {useState} from 'react';
// Components to be displayed
import Inbox from './Inbox';
import SendMessage from './SendMessage';
// Other components on the page which you can ignore
import Navbar from '../Layout/Navbar';
import Copyright from '../Layout/Copyright';
import SendMail from './SendMail';
//Material ui for styling
import AppBar from '#material-ui/core/AppBar';
import CssBaseline from '#material-ui/core/CssBaseline';
import Divider from '#material-ui/core/Divider';
import Drawer from '#material-ui/core/Drawer';
import Hidden from '#material-ui/core/Hidden';
import IconButton from '#material-ui/core/IconButton';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import MailIcon from '#material-ui/icons/Mail';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import Link from '#material-ui/core/Link';
import ControlPointIcon from '#material-ui/icons/ControlPoint';
const drawerWidth = 240;
const list = [
{id: 1, title: 'Singing lessons', message: 'We are practicing at 5:00'},
{id: 2, title: 'Meeting', message: 'Hi Guys, we can meet during lunch at Nandos'},
{id: 3, title: 'New Product release', message: 'Hi guys, we are encouraging everyone to buy in on the new product'}
]
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex'
},
drawer: {
[theme.breakpoints.up('sm')]: {
width: drawerWidth,
flexShrink: 0,
zIndex: '0'
}
},
appBar: {
[theme.breakpoints.up('sm')]: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
},
},
menuButton: {
marginRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
display: 'none',
},
},
// necessary for content to be below app bar
toolbar: theme.mixins.toolbar,
drawerPaper: {
width: drawerWidth,
},
content: {
flexGrow: 1,
padding: theme.spacing(3)
},
listItemStyle: {
marginTop: '50px'
},
newMessage: {
fontSize: '40px',
backgroundColor: '#64b5f6',
borderRadius: '50px',
color: '#fff',
position: 'absolute',
bottom: '50px',
right: '50px',
'&:hover': {
backgroundColor: '#1976d2'
}
}
}));
// Main component
const Message = (props) => {
const { window } = props;
const classes = useStyles();
const theme = useTheme();
const [mobileOpen, setMobileOpen] = useState(false);
const [tabTracker, setTabTracker] = useState('Inbox');
const renderSwitch = (param) => {
switch(param) {
case 'Inbox':
return <Inbox />;
case 'Send email':
return <SendMail />;
case 'Drafts':
return 'Draft';
case 'Trash':
return 'Trash';
case 'Spam':
return 'Spam';
case 'newMessage':
return <SendMessage />;
default:
return 'foo';
}
}
const tabControl = (e) => {
setTabTracker(e.target.firstChild.data);
//renderSwitch(tabTracker);
}
const newMsg = (e) => {
setTabTracker(e.target.attributes[5].value);
//renderSwitch(tabTracker);
}
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List className={classes.listItemStyle}>
{['Inbox', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text} onClick={tabControl}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Trash', 'Spam'].map((text, index) => (
<ListItem button key={text} onClick={tabControl}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const container = window !== undefined ? () => window().document.body : undefined;
return (
<React.Fragment>
<span>
<Navbar />
</span>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer} aria-label="mailbox folders">
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Hidden smUp implementation="css">
<Drawer
container={container}
variant="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={mobileOpen}
onClose={handleDrawerToggle}
classes={{
paper: classes.drawerPaper,
}}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer
classes={{
paper: classes.drawerPaper,
}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
{renderSwitch(tabTracker)}
<Link href="#">
<ControlPointIcon value="newMessage" primary="newMessage" className= {classes.newMessage} onClick={newMsg} />
</Link>
</main>
</div>
<Copyright />
</React.Fragment>
);
}
// ResponsiveDrawer.propTypes = {
// /**
// * Injected by the documentation to work in an iframe.
// * You won't need it on your project.
// */
// window: PropTypes.func,
// };
export default Message;
e.target gives you the element on which click was actually triggered, so when you have multiple elements as children within ListItem, its possible that you would have clicked on ListItemIcon and hence e.target is listItemIcon which was not your intention as it will give you an incorrect value for e.target.firstChild.data
So you could either use e.currentTarget which will give you the element on which the onClick listener is attached or you could simply pass on the information to tabControl by using an inline arrow function
const tabControl = (value) => {
setTabTracker(value);
}
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List className={classes.listItemStyle}>
{['Inbox', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text} onClick={() => tabControl(text)}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Trash', 'Spam'].map((text, index) => (
<ListItem button key={text} onClick={() => tabControl(text)}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);

how to render components based on the value of props?

I have a parent component Dashboard.js. Here I have three values of state namely yesterday, lastWeek, lastMonth and I'm passing this value to my child component. Now I want to render my data depending on my child component. The problem is I'm using componentDidMount() lifecycle method which is rendering the child component only once. How do I render the data based on the props passed to the child component? The parent component is a react hook and the child component called DataFetchDetails is a class based component. Attaching their respective codes
Parent Component :- Dashboard.js
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import { Tabs, Tab, Grid } from '#material-ui/core';
import AppBar from '#material-ui/core/AppBar';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
import PropTypes from 'prop-types';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select'
import {
TotalUsers,
LoggedInUsers,
TimePicker,
UnregisteredUsers
} from './components';
import DataFetchDetails from './components/DataFetchDetails';
const useStyles = makeStyles(theme => ({
root: {
paddingTop: theme.spacing(4),
padding: theme.spacing(4)
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
<Box p={3}>{children}</Box>
</Typography>
);
}
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
const Dashboard = () => {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const [period, setPeriod] = React.useState("yesterday");
const handleChange1 = event => {
setPeriod(event.target.value);
};
return (
<div className={classes.root}>
<Select
labelId="demo-simple-select-label"
id="demo-sample-select"
value={time}
onChange={handleChange1}
>
<MenuItem value={"yesterday"}>Previous day</MenuItem>
<MenuItem value={"lastWeek"}>Last Week</MenuItem>
<MenuItem value={"lastMonth"}>Last Month</MenuItem>
</Select>
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
<Tab label="CONSENT DETAILS" {...a11yProps(0)} />
<Tab label="ACCOUNT DETAILS" {...a11yProps(1)} />
<Tab label="DATA FETCH DETAILS" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
</TabPanel>
<TabPanel value={value} index={1}>
</TabPanel>
<TabPanel value={value} index={2}>
<DataFetchDetails period={period} handlePeriodChange1={handleChange1} />
</TabPanel>
</div>
</div>
);
};
export default Dashboard;
Child component DataFetchDetails.js :-
import React from 'react';
import {
Card,
CardHeader,
Button,
Divider,
CardContent,
TextField,
CardActions,
FormControl,
InputLabel,
Select,
MenuItem
} from '#material-ui/core';
import Paper from '#material-ui/core/Paper';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import axios from 'axios';
import 'izitoast/dist/css/iziToast.min.css'; // loading css
import iziToast from 'izitoast/dist/js/iziToast.min.js'; // you have access to iziToast now
import 'izitoast/dist/css/iziToast.min.css';
const url = 'MY_ENDPOINT_URL';
export default class DataFetchDetails extends React.Component {
constructor(props) {
super(props);
this.state = {
items : [],
isLoaded : true,
renderJsx: false,
}
}
componentDidMount() {
this.setState({period: this.props.period});
const periodStatus = {
period : this.props.period
};
{console.log("Props period = ",this.props.period)}
axios.post(url, periodStatus)
.then((response) => {
this.setState({period : this.props.period})
this.setState({items : [response.data]});
.catch((error) => {
console.log("Error");
});
}
render() {
let {isLoaded, items, renderJsx } = this.state;
if(!isLoaded) {
return <div>Loading</div>
}
else {
return (
<div>
<div>
<Card className="Lock-user"
>
<form >
<CardHeader
title=""
/>
<Divider></Divider>
<CardContent id="form-input" className=""
>
</CardContent>
<Divider></Divider>
</form>
</Card>
</div>
<div>
<Card>
<Paper>
<Table>
<TableHead>
<TableRow>
<TableCell> success </TableCell>
<TableCell align="right">failure</TableCell>
<TableCell align="right">inProgress</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ items.map(item => (
<TableRow key={item.success}>
<TableCell component="th" scope="row">
{item.success}
</TableCell>
<TableCell align="right">{item.failure}</TableCell>
<TableCell align="right">{item.inProgress}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{console.log("Props period render = ",this.props.period)}
</Paper>
</Card>
</div>
</div>
);
}
}
}
the backend and the api works fine. I want to re render my child component based on the value of the period. How do I solve this problem?
You you compare the props i.e prevProps and current props(this.props) object inside
ComponentDidUpdate
lifecycle method to re-render the child component based on props.
As ComponentWillReceiveProps is deprecated now.
https://egghead.io/lessons/react-refactor-componentwillreceiveprops-to-getderivedstatefromprops-in-react-16-3
Go through the react lifecycle docs or https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/postrender_with_componentdidupdate.html.
Use componentWillRecieveProps in child component.
componentWillRecieveProps(props) {
// props => new props passed
// this.props => current props
}
I hope that helps.

Material-ui adding Link component from react-router

I'm struggling to add <Link/> component to my material-ui AppBar
This is my navigation class:
class Navigation extends Component {
constructor(props) {
super(props)
}
render() {
var styles = {
appBar: {
flexWrap: 'wrap'
},
tabs: {
width: '100%'
}
}
return (
<AppBar showMenuIconButton={false} style={styles.appBar}>
<Tabs style={styles.tabs}>
<Tab label='Most popular ideas'/>
<Tab label='Latest ideas' />
<Tab label='My ideas' />
</Tabs>
</AppBar>
)
}
}
Which looks okay:
Tabs are clickable, have fluid animations, that's cool. But how do I wire them up together with react-router and its' <Link/> component?
I've tried adding onChange listener like that:
<Tab
label='My ideas'
onChange={<Link to='/myPath'></Link>}
/>
However I'm getting following error:
Uncaught Invariant Violation: Expected onChange listener to be a function, instead got type object
If I try to wrap <Tab/> component into <Link/> component, I'm getting error that <Tabs/> component accepts only <Tab/> component.
This doesn't work either (no error is being produced, but clicking on Tab does not bring me to the path):
<Tab label='Most popular ideas'>
<Link to='/popular'/>
</Tab>
How do I make <Link/> component work together with <Tabs> and <AppBar>? If that's not possible, I can use any other component from material-ui library to form a proper menu.
For Material UI 1.0 with Typescript: see this post by #ogglas below.
For Material-UI 1.0 with plain JS:
<Tabs value={value} onChange={this.handleChange}>
{
this.props.tabs.map(
({label, path})=><Tab key={label}
label={label}
className={classes.tabLink}
component={Link}
to={path} />
)
}
</Tabs>
And classes.tabLink is defined as:
tabLink : {
display:"flex",
alignItems:"center",
justifyContent:"center"
}
How this works?
All the mui 1.0 components inheriting from ButtonBase, support a component prop, see ButtonBase. The idea is to allow you to control what the component renders as its wrapper/root element. Tab also has this feature although at the time of writing this answer this prop is not documented explicitly, but as Tab inherits from ButtonBase, all its props carry over (and the documentation does cover this).
Another feature of ButtonBase is that all the extra props, not in use by ButtonBase or inherited component, are spread over the specified component. We have used this behavior to send the to prop used by Link by giving it to Tab control. You can send any additional props in the same way. Note that this is documented explicitly for both ButtonBase and Tab.
Thanks #josh-l for asking this to be added.
here's how you can do it now:
<Tabs onChange={this.changeTab} value={value}>
<Tab value={0} label="first" containerElement={<Link to="/first"/>} />
<Tab value={1} label="second" containerElement={<Link to="/second"/>}/>
<Tab value={2} label="third" containerElement={<Link to="/third"/>} />
</Tabs>
You can try this simple method
<Tab label='Most popular ideas' to='/myPath' component={Link} />
This is solved using the <Link /> from material-ui instead of directly using the <Link /> or <NavLink /> from react-router. The example for the same can be found in the documentation here.
https://material-ui.com/components/links/
Also <Button /> tag has a component prop to achieve this
<Button color="inherit" component={Link} to={"/logout"}>Logout</Button>
An extensive discussion on this can be found here
https://github.com/mui-org/material-ui/issues/850
Since we are using TypeScript I could not use #hazardous solutions. This is how we implemented routing for material-ui v1.0.0-beta.16 and react-router 4.2.0. The reason why we are splitting this.props.history.location.pathname is because we need to access /renewals/123 for example. If we did not do this we would get the following warning and no tab would be displayed as active: Warning: Material-UI: the value provided '/renewals/123' is invalid
Complete code with imports:
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as ReactRouter from "react-router";
import * as PropTypes from "prop-types";
import { Switch, Route, Redirect, Link } from "react-router-dom";
import { Cases } from './../Cases';
import { SidePane } from './../SidePane';
import { withStyles, WithStyles } from 'material-ui/styles';
import Paper from 'material-ui/Paper';
import Tabs, { Tab } from 'material-ui/Tabs';
import { withRouter } from "react-router-dom";
import Badge from 'material-ui/Badge';
import Grid from 'material-ui/Grid';
import { Theme } from 'material-ui/styles';
import SimpleLineIcons from '../../Shared/SimpleLineIcons'
interface IState {
userName: string;
}
interface IProps {
history?: any
}
const styles = (theme: Theme) => ({
root: theme.typography.display1,
badge: {
right: '-28px',
color: theme.palette.common.white,
},
imageStyle:{
float: 'left',
height: '40px',
paddingTop: '10px'
},
myAccount: {
float: 'right'
},
topMenuAccount: {
marginLeft: '0.5em',
cursor: 'pointer'
}
});
type WithStyleProps = 'root' | 'badge' | 'imageStyle' | 'myAccount' | 'topMenuAccount';
class Menu extends React.Component<IProps & WithStyles<WithStyleProps>, IState> {
constructor(props: IProps & WithStyles<WithStyleProps>) {
super(props);
this.state = {
userName: localStorage.userName ? 'userName ' + localStorage.userName : ""
}
}
componentDidMount() {
this.setState({ userName: localStorage.userName ? localStorage.userName : "" })
}
logout(event: any) {
localStorage.removeItem('token');
window.location.href = "/"
}
handleChange = (event: any, value: any) => {
this.props.history.push(value);
};
render() {
const classes = this.props.classes;
let route = '/' + this.props.history.location.pathname.split('/')[1];
return (
<div>
<Grid container spacing={24}>
<Grid item xs={12} className={classes.root}>
<img src="/Features/Client/Menu/logo.png" alt="Logo" className={classes.imageStyle} />
<div className={this.props.classes.myAccount}>
<span><span className={this.props.classes.topMenuAccount}>MY ACCOUNT</span><span className={classes.topMenuAccount}><SimpleLineIcons iconName={'user'} />▾</span></span>
<span onClick={this.logout} className={classes.topMenuAccount}><SimpleLineIcons iconName={'logout'} /></span>
</div>
</Grid>
<Grid item xs={12} >
<div className="route-list">
<Tabs
value={route}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
>
<Tab label="Overview" value="/" />
<Tab label={<Badge classes={{ badge: classes.badge }} badgeContent={this.props.caseRenewalCount} color="primary">
Renewals
</Badge>} value="/renewals" />
</Tabs>
</div>
</Grid>
</Grid>
</div>
);
}
}
export default withStyles(styles)(withRouter(Menu))
TypeScript implementation of the router-driven tabs.
For those who look for the TypeScript implementation. Easy configurable. Driven by tabs configuration.
interface ITabsPageProps {
match: match<{page: string}>;
history: History;
}
const tabs = [{
label: 'Fist Tab',
link: 'fist-tab',
component: <FirstTabContent/>
}, {
label: 'Second Tab',
link: 'second-tab',
component: <SecondTabContent/>
}, {
label: 'Third Tab',
link: 'third-tab',
component: <ThirdTabContent/>
}];
export class TabsPage extends React.Component<ITabsPageProps> {
handleChange(tabLink: string) {
this.props.history.push(`/tabs-page/${tabLink}`);
}
render() {
const currentTab = this.props.match.params.page;
const selectedTab = tabs.find(tab => currentTab === tab.link);
return (
<Fragment>
<Tabs
value={currentTab}
onChange={(event, value) => this.handleChange(value)}
>
{tabs.map(tab => (
<Tab
key={tab.link}
value={tab.link}
label={tab.label}
/>
))}
</Tabs>
{selectedTab && selectedTab.component}
</Fragment>
);
}
}
Here's another implementation of React with hooks, Material-UI with tabs, React Router with Link, and TypeScript.
import * as React from "react";
import { BrowserRouter as Router, Route, Redirect, Switch, Link, LinkProps } from 'react-router-dom';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import { default as Tab, TabProps } from '#material-ui/core/Tab';
import Home from './Home';
import ProductManagement from './ProductManagement';
import Development from './Development';
import HomeIcon from '#material-ui/icons/Home';
import CodeIcon from '#material-ui/icons/Code';
import TimelineIcon from '#material-ui/icons/Timeline';
const LinkTab: React.ComponentType<TabProps & LinkProps> = Tab as React.ComponentType<TabProps & LinkProps>;
function NavBar() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
return (
<div >
<AppBar position="static" >
<Tabs value={value} onChange={handleChange} centered>
<LinkTab label='Home' icon={ <HomeIcon />} component={Link} to="/" />
<LinkTab label='Development' icon={<CodeIcon />} component={Link} to="/dev" />
<LinkTab label='Product Management' icon={<TimelineIcon />} component={Link} to="/pm" />
</Tabs>
</AppBar>
</div>
)
};
export default function App() {
return (
<Router>
<div>
<NavBar />
<Switch>
<Route exact path="/" component={ Home } />
<Route exact path="/dev" component={ Development } />
<Route exact path="/pm" component={ ProductManagement } />
<Redirect from="/" to="/" />
</Switch>
</div>
</Router>
)
}
So my work-around for this solution has been quite reliable, though it may be more manual of a solution than what you're looking to do.
The strategy that I've been using is to actually not even use the Link Component. Instead, you'll utilize the Tabs onChange property as a callback that can respond to Tab clicks, and track location manually with Props on the Parent.
You can import a utility called History from react-router that will allow you to manually push locations. While using React-Router, your component tree will have access to Location prop that has a pathname key with the string of your current location.
We will manually parse this string into the components that make up your current URL, then use a Switch statement to decide both which tab is currently selected and also where to link to when a tab is clicked. (This gives you a fair amount of control over navigation)
( e.g. ['', 'latest'] )
Here is a mock up of what your component MAY look like after integrating this solution.
import React from 'react';
import {History} from 'react-router';
function parseLocation(location) {
if (String(location)) {
var locationArray = location.split('/');
return locationArray;
} else {
return false;
}
};
function filterPath(path) {
let locationArray = parseLocation(path);
return locationArray[locationArray.length - 1];
};
var Navigation = React.createClass({
mixins: [History],
getPage() {
if (this.props.location.pathname) {
let pathname = this.props.location.pathname;
let pageName = filterPath(pathname);
return pageName;
} else {
return false;
}
},
decideContent() {
let page = this.getPage();
let content;
switch(page) {
case 'popular':
content = 0;
case 'latest':
content = 1;
case 'myideas':
content = 2;
default:
content = 0;
}
return content;
},
handleTabChange(value) {
let location = false;
switch (value) {
case 0:
location = 'popular';
break;
case 1:
location = 'latest';
break;
case 2:
location = 'myideas';
break;
}
if (location && location !== this.getPage()) {
this.history.pushState(null, '/'+location);
}
},
render() {
var styles = {
appBar: {
flexWrap: 'wrap'
},
tabs: {
width: '100%'
}
};
let content = this.decideContent();
let tabs = <Tabs
onChange={this.handleTabChange}
value={content}
>
<Tab label="Most Popular Ideas" value={0} />
<Tab label="Latest Ideas" value={1} />
<Tab label="My Ideas" value={2} />
</Tabs>;
return (
<AppBar showMenuIconButton={false} style={styles.appBar}>
{tabs}
</AppBar>
);
}
});
Check this link, I implemented the solution and worked for me
Composition in material UI
If you use NextJs, you can do it like that, and create your own component.
*i didn`t wrap the Tab with 'a' tag, because it added automatically
const WrapTab = (props) => {
const { href } = props
return (
<Link href={href} style={{ width: "100%" }}>
<Tab {...props} />
</Link>
)}
and then this is your return
<Tabs
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
variant="fullWidth"
>
<WrapTab href="/testPage/?tab=0" icon={<MenuIcon />} />
<WrapTab href="/testPage/?tab=1" icon={<StampIcon2 />} />
<WrapTab href="/testPage/?tab=2" icon={<ShopIcon />} />
<WrapTab href="/testPage/?tab=3" icon={<PenIcon />} />
<WrapTab href="/testPage/?tab=4" icon={<ProfileIcon />} />
</Tabs>
link to material-ui docs:
https://material-ui.com/guides/composition/
This seems to work for me
import { Link as RouterLink } from 'react-router-dom';
import Link from '#mui/material/Link';
<Link to={menuItem.url} component={RouterLink} aria-current="page">
{menuItem.label}
</Link>
For anyone looking to wrap Material-ui Link with Next.js Link
import Link from "next/link"
import MuiLink from "#material-ui/core/Link"
const CustomNextLink = ({href, alt}) => ({children, ...rest}) => (
<Link href={href} alt={alt}>
<MuiLink {...rest}>
{children}
</MuiLink>
</Link>)
Then pass it to you Tab component
<Tab
key={...}
label={title}
icon={icon}
component={CustomNextLink({href: to, alt: title})}
style={...}
className={...}
classes={{selected: ...}}
{...a11yProps(index)}
/>
Use the href="" option as shown below:
<Tab
href="/en/getting-started"
label="Contact US"
style={{ color: "white", textDecoration: "none" }}
/>
To remove the ripple effect on clicking, use the option disableRipple

Categories

Resources