React passing down a function that modifies a third component - reactjs

I have been trying to create a component per function in my app, but I am facing the following issue.
I have the component DisplayAllData that sends the data and an actionable button to DisplayDataWithButton, the issue is that when someone clicks on the Button send in the props, the function modifies the state of the parent component, which is also sent as a parameter to FullScreenDialog, and that throws a Warning: Cannot update a component while rendering a different component.
I designed the components in this particular way because:
DisplayAllData is the only function that has the data to render and the actionable button. (Model)
DisplayDataWithButton only renders the data and displays the actionable components for that particular data, in this case a button that opens a Dialog in screen. (Viewer)
You can find a running example here: https://codesandbox.io/s/material-demo-forked-8oyef
import React from "react";
import Button from "#material-ui/core/Button";
import DisplayDataWithButton from "./DisplayDataWithButton";
import FullScreenDialog from "./fullscreendialog";
export default function App(props) {
const [openFullScreen, setopenFullScreen] = React.useState(false);
var items = ["John", "Melinda"];
var dataDisplayFunction = (data) => {
return data.map((item) => {
return [
item,
<Button
color="success"
size="small"
className="px-2"
variant="contained"
onClick={setopenFullScreen()}
>
Show Dialog
</Button>
];
});
};
return (
<>
<DisplayDataWithButton
shapeDataFunction={dataDisplayFunction}
data={items}
/>
<FullScreenDialog open={openFullScreen} />
</>
);
}
DisplayDataWithButton.js
export default function DisplayDataWithButton(props) {
return props.shapeDataFunction(props.data);
}
I suspect that there is another way to implement this model, any suggestion, or ideas on how to fix this one.
Thanks

A couple of things: "I have been trying to create a component per function in my app". Forget that - the pattern you have opted for here is called render props but I don't see how it is necessary. Keep it simple. If a big component is simpler to understand than a small component I always opt for the bigger component. Splitting your components will not magically make them easier to understand.
All of the warnings have been dealt with. Most of them were simple mistakes, for example: onClick={setopenFullScreen()} should be onClick={setopenFullScreen}. You can compare your sandbox with my sandbox for all of the changes.
import React from "react";
import ReactDOM from "react-dom";
import Button from "#material-ui/core/Button";
import FullScreenDialog from "./fullscreendialog";
export default function App() {
const [openFullScreen, setopenFullScreen] = React.useState(false);
const items = ["John", "Melinda"];
return (
<>
{items.map((item) => [
item,
<Button
key={item}
color="primary"
size="small"
className="px-2"
variant="contained"
onClick={() => setopenFullScreen((prev) => !prev)}
>
Show Dialog
</Button>
])}
<FullScreenDialog open={openFullScreen} />
</>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
import React from "react";
import { makeStyles } from "#material-ui/core";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import ListItemText from "#material-ui/core/ListItemText";
import ListItem from "#material-ui/core/ListItem";
import List from "#material-ui/core/List";
import Divider from "#material-ui/core/Divider";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Typography from "#material-ui/core/Typography";
import CloseIcon from "#material-ui/icons/Close";
import Slide from "#material-ui/core/Slide";
const useStyles = makeStyles((theme) => ({
appBar: {
position: "relative"
},
title: {
marginLeft: theme.spacing(2),
flex: 1
}
}));
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function FullScreenDialog(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
fullScreen
open={props.open}
onClose={handleClose}
TransitionComponent={Transition}
>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<List>
<ListItem button>
<ListItemText primary="Phone ringtone" secondary="Titania" />
</ListItem>
<Divider />
<ListItem button>
<ListItemText
primary="Default notification ringtone"
secondary="Tethys"
/>
</ListItem>
</List>
</Dialog>
</div>
);
}

Related

signOut being passed as props from one component to App.js, but on click on logout icon doesn't allow to logout from system

In my react hooks signOut is being passed as props from DashboardNav.js component to App.js. On click on logout icon in App.js is not actually logging out from the system. I am actually using gapi-scirpt for performing the logout operation, could someone please advise me how can i achieve this ?
CodeSandBox link:
https://codesandbox.io/s/fervent-ioana-pz5jyz?file=/src/dashboardNav.js:0-1199
// App.js
import React, { useEffect, useState } from "react";
import DashboardNavbar from "./dashboardNav";
import { gapi } from "gapi-script";
export default function App() {
const [isMobileNavOpen, setMobileNavOpen] = useState(false);
const { signOut } = () => {
alert("hello");
const auth2 = gapi.auth2.getAuthInstance();
if (auth2 != null) {
auth2.signOut().then(
auth2.disconnect().then(console.log("LOGOUT SUCCESSFUL")),
localStorage.removeItem("loginEmail"),
localStorage.removeItem("userImage"),
//history.push("/"),
console.log("Logged out successfully !")
);
}
};
return (
<div className="App">
<DashboardNavbar
logout={signOut}
onMobileNavOpen={() => setMobileNavOpen(true)}
/>
</div>
);
}
// dashboardNav.js
import { useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import PropTypes from "prop-types";
import {
AppBar,
Badge,
Box,
Hidden,
IconButton,
Toolbar
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import NotificationsIcon from "#material-ui/icons/NotificationsOutlined";
import InputIcon from "#material-ui/icons/Input";
const DashboardNavbar = ({ onMobileNavOpen, signOut, ...rest }) => {
const [notifications] = useState([]);
return (
<AppBar elevation={0} {...rest} style={{ background: "#1976D2" }}>
<Toolbar>
<RouterLink to="/">
<img alt="Logo" src="images/simpro.PNG" width="80px" />
</RouterLink>
<Box sx={{ flexGrow: 1 }} />
<Hidden mdDown>
<IconButton color="inherit" onClick={signOut}>
<InputIcon />
</IconButton>
</Hidden>
<Hidden lgUp>
<IconButton color="inherit" onClick={onMobileNavOpen}>
<MenuIcon />
</IconButton>
</Hidden>
</Toolbar>
</AppBar>
);
};
DashboardNavbar.propTypes = {
onMobileNavOpen: PropTypes.func
};
export default DashboardNavbar;

makeStyle does not work with custom component

I am trying to make a simple navbar using material ui with a few buttons and a custom drop down menu component. When I try to style it using the makeStyle hook, the styling only applies to the material ui's buttons and heading but not the custom drop down component.
import React, { useContext } from "react";
import { makeStyles } from "#material-ui/core/styles";
import { AppBar, Toolbar, Typography } from "#material-ui/core";
import DropDown from "./DropDown";
import { Button } from "#material-ui/core";
import { AlgoContext } from "../AlgoContext";
const useStyles = makeStyles((theme) => ({
item: {
marginRight: theme.spacing(5),
},
}));
const MainHeader = () => {
const classes = useStyles();
const [algo, setAlgo] = useContext(AlgoContext);
return (
<div>
<AppBar elevation={0} position='static'>
<Toolbar>
<Typography variant='h6' className={classes.item}>
Pathfinding Visualiser
</Typography>
<Button variant='contained' className={classes.item}>
Visualise {algo.type}
</Button>
<DropDown className={classes.item}></DropDown>
<Button variant='contained' className={classes.item}>
Clear walls
</Button>
<Button variant='contained' className={classes.item}>
Clear path
</Button>
</Toolbar>
</AppBar>
</div>
);
};
export default MainHeader;
className is a default attribute of React element. You can not style your custom component by passing style object via className. Instead of that, you should pass it as a prop to DropDown component. Try this:
const MainHeader = () => {
const classes = useStyles();
const [algo, setAlgo] = useContext(AlgoContext);
return (
<DropDown itemStyle={classes.item}></DropDown>
);
};
export default MainHeader;
const DropDown = (props) => {
...
return (
<div className={props.itemStyle}>
...
</div>
)
}

Customizing/Disabling Material-UI Dialog's Touch Mechanics

may somebody please help me customize/disable the Material-UI Dialog's touch mechanics? I have the Cancel and Confirm buttons, and that's just what I want to use to close the dialog. However, making a selection from a dropdown menu caused the dialog to autoclose according to the doc. And I couldn't see how to do that in the doc itself.
The problem
The problem I have is that the auto-closing is doing the job I want the cancel button to do. i.e: Closing the dialog and empty an array.
I'd appreciate your help.
Any special reason to use the Material-UI Simple Dialog?
From your question, seems like a Confirmation dialog is what you're looking for :)
Simple dialog touch mechanism
Touch mechanics:
Choosing an option immediately commits the option and closes the menu
Touching outside of the dialog, or pressing Back, cancels the action and closes the dialog
In the other hand, matching your needs:
Confirmation dialogs
Confirmation dialogs require users to explicitly confirm their choice before an option is committed. For example, users can listen to multiple ringtones but only make a final selection upon touching “OK”.
Touching “Cancel” in a confirmation dialog, or pressing Back, cancels the action, discards any changes, and closes the dialog.
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import DialogTitle from '#material-ui/core/DialogTitle';
import DialogContent from '#material-ui/core/DialogContent';
import DialogActions from '#material-ui/core/DialogActions';
import Dialog from '#material-ui/core/Dialog';
import RadioGroup from '#material-ui/core/RadioGroup';
import Radio from '#material-ui/core/Radio';
import FormControlLabel from '#material-ui/core/FormControlLabel';
const options = [
'None',
'Atria',
'Callisto',
'Dione',
'Ganymede',
'Hangouts Call',
'Luna',
'Oberon',
'Phobos',
'Pyxis',
'Sedna',
'Titania',
'Triton',
'Umbriel',
];
function ConfirmationDialogRaw(props) {
const { onClose, value: valueProp, open, ...other } = props;
const [value, setValue] = React.useState(valueProp);
const radioGroupRef = React.useRef(null);
React.useEffect(() => {
if (!open) {
setValue(valueProp);
}
}, [valueProp, open]);
const handleEntering = () => {
if (radioGroupRef.current != null) {
radioGroupRef.current.focus();
}
};
const handleCancel = () => {
onClose();
};
const handleOk = () => {
onClose(value);
};
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<Dialog
disableBackdropClick
disableEscapeKeyDown
maxWidth="xs"
onEntering={handleEntering}
aria-labelledby="confirmation-dialog-title"
open={open}
{...other}
>
<DialogTitle id="confirmation-dialog-title">Phone Ringtone</DialogTitle>
<DialogContent dividers>
<RadioGroup
ref={radioGroupRef}
aria-label="ringtone"
name="ringtone"
value={value}
onChange={handleChange}
>
{options.map((option) => (
<FormControlLabel value={option} key={option} control={<Radio />} label={option} />
))}
</RadioGroup>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel} color="primary">
Cancel
</Button>
<Button onClick={handleOk} color="primary">
Ok
</Button>
</DialogActions>
</Dialog>
);
}
ConfirmationDialogRaw.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
value: PropTypes.string.isRequired,
};
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
maxWidth: 360,
backgroundColor: theme.palette.background.paper,
},
paper: {
width: '80%',
maxHeight: 435,
},
}));
export default function ConfirmationDialog() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('Dione');
const handleClickListItem = () => {
setOpen(true);
};
const handleClose = (newValue) => {
setOpen(false);
if (newValue) {
setValue(newValue);
}
};
return (
<div className={classes.root}>
<List component="div" role="list">
<ListItem button divider disabled role="listitem">
<ListItemText primary="Interruptions" />
</ListItem>
<ListItem
button
divider
aria-haspopup="true"
aria-controls="ringtone-menu"
aria-label="phone ringtone"
onClick={handleClickListItem}
role="listitem"
>
<ListItemText primary="Phone ringtone" secondary={value} />
</ListItem>
<ListItem button divider disabled role="listitem">
<ListItemText primary="Default notification ringtone" secondary="Tethys" />
</ListItem>
<ConfirmationDialogRaw
classes={{
paper: classes.paper,
}}
id="ringtone-menu"
keepMounted
open={open}
onClose={handleClose}
value={value}
/>
</List>
</div>
);
}
you can have a look of the working dialog here:
https://4zgol.csb.app/
Hope it helps and if not, feel free to explain more about the problem or even add a code snippet :)

React Dialog box closed issue

I want to use a dialog box of material UI. I am navigating that dialog box from the right side menu(sidebars -> user registration), using another component. I just want to open that dialog box on the current page home or about us. It is a user registration dialog. Can you help on that?
When I tried to open the user registration dialog, I am unable to open the dialog box in the current page, that's why I have created a separate component for the dialog box component.
I want to open that dialog when I select the side menu option. The dialog box should be open in the current page.
This is the code sandbox link. https://codesandbox.io/s/immutable-sound-ggj4w
App js
import React from "react";
import "./styles.css";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./home";
import About from "./about";
import Dialog from "./dialog";
import SideMenu from "./sidemu";
export default function App() {
return (
<div className="App">
<BrowserRouter>
<SideMenu />
{/* <Switch> */}
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/sidemenu" component={SideMenu} />
<Route exact path="/dialog" component={Dialog} />
{/* </Switch> */}
</BrowserRouter>
{/* <h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2> */}
</div>
);
}
home js
import React, { Component } from "react";
class Home extends Component {
render() {
return (
<div>
<div>Home</div>
</div>
);
}
}
export default Home;
about us
import React, { Component } from "react";
class Home extends Component {
render() {
return (
<div>
<div>about us</div>
</div>
);
}
}
export default Home;
dialog js
import React from 'react';
import Button from '#material-ui/core/Button';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open alert dialog
</Button>
<Dialog
open={true}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Use Google's location service?"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Let Google help apps determine location. This means sending anonymous location data to
Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Disagree
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
</div>
);
}
sidemenu js
import React from 'react';
import './styles.css';
import { Link } from 'react-router-dom';
import { makeStyles } from '#material-ui/core/styles';
import Drawer from '#material-ui/core/Drawer';
import { List, ListItemIcon, ListItem, ListItemText } from '#material-ui/core';
import MenuRoundedIcon from '#material-ui/icons/MenuRounded';
import HomeRoundedIcon from '#material-ui/icons/HomeRounded';
import MenuBookRoundedIcon from '#material-ui/icons/MenuBookRounded';
const useStyles = makeStyles({
list: {
width: 250,
},
fullList: {
width: 'auto',
},
});
export default function TemporaryDrawer() {
const classes = useStyles();
const [state, setState] = React.useState({
top: false,
left: false,
bottom: false,
right: false,
});
const toggleDrawer = (side, open) => event => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [side]: open });
};
const sideList = side => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer(side, false)}
onKeyDown={toggleDrawer(side, false)}
>
<List>
<Link className="right-menu-data" to="/">
<ListItem button>
<ListItemIcon>
<HomeRoundedIcon />
</ListItemIcon>
<ListItemText>Home</ListItemText>
</ListItem>
</Link>
<Link className="right-menu-data" to="/about"><ListItem button>
<ListItemIcon>
<MenuBookRoundedIcon />
</ListItemIcon>
<ListItemText>About us</ListItemText>
</ListItem>
</Link>
<Link className="right-menu-data" to="/dialog"><ListItem button>
<ListItemIcon>
<MenuBookRoundedIcon />
</ListItemIcon>
<ListItemText>User Registration</ListItemText>
</ListItem>
</Link>
</List>
</div>
);
return (
<div>
<MenuRoundedIcon className="careerpedia-menu-bars" onClick={toggleDrawer('right', true)} />
<Drawer anchor="right" open={state.right} onClose={toggleDrawer('right', false)}>
{sideList('right')}
</Drawer>
</div>
);
}
One way to achieving this is to factor the logic to open/close the modal out of the dialog component to a "central" location, preferably in the app, but outside the router. This allows the dialog to be opened/closed from a single location by callbacks that can be passed around the app, and not be coupled to any specific route your app is on.
A quick refactoring of your code:
dialog.js
const AlertDialog = ({ open, onAgree, onClose, onDisagree }) => (
<Dialog
open={open}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Use Google's location service?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Let Google help apps determine location. This means sending anonymous
location data to Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onDisagree} color="primary">
Disagree
</Button>
<Button onClick={onAgree} color="primary" autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
);
App.js
export default function App() {
const [open, setOpen] = useState(false);
const openDialog = () => setOpen(true);
const closeDialog = () => setOpen(false);
const onAgreeHandler = () => {
closeDialog();
alert("AGREED");
};
const onDisgreeHandler = () => {
closeDialog();
alert("DISAGREED");
};
const onCloseHandler = (event, reason) => {
closeDialog();
alert(`Dialog closed. Reason: ${reason}`);
};
return (
<div className="App">
<BrowserRouter>
<SideMenu openDialog={openDialog} /> // Pass dialog open callback to sidemenu
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/dialog" component={Dialog} />
</BrowserRouter>
<Button variant="outlined" color="primary" onClick={openDialog}>
Open alert dialog test
</Button>
<Dialog
open={open}
onAgree={onAgreeHandler}
onClose={onCloseHandler}
onDisagree={onDisgreeHandler}
/>
</div>
);
}
sidemu.js
export default function TemporaryDrawer({ openDialog }) { // Accept openDialog callback
...
{ /* Add new menu list item (not in a Link!) */ }
<ListItem button onClick={openDialog}> // Set onClick handler to openDialog callback
<ListItemIcon>
<MenuBookRoundedIcon />
</ListItemIcon>
<ListItemText>Trigger dialog?</ListItemText>
</ListItem>
NOTE: As your app grows in size/complexity this pattern may become a little unwieldy (if you need to open a dialog by any further descendants) and you'll likely want to switch to using a react context provider/consumer specifically for the dialog, or use a state management system like Redux so you can dispatch simple actions to open/close it.

Using Material-UI Box Component with the Drawer Compoment

The Material-UI Box component allows us to reference other components as follows:
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
const NewButton = ({ children }) => (
<Box compoment={Button} px={3} py={1} color="white" bgcolor="primary.dark">
{children}
</Box>
)
This works just as I want it to. However, let me now try it with the Drawer component:
import Drawer from "#material-ui/core/Drawer";
import Box from "#material-ui/core/Box";
const NewDrawer = ({ children, open }) => {
return (
<Box component={Drawer} width="300px" bgcolor="secondary.dark">
{children}
</Box>
)
}
This does not work.
Any idea why not and how I can get it to work?
Thanks.
As per Material UI doc, For the Drawer component, we have to pass the open prop as true.
And also need to pass the drawer content like below,
<Drawer open={true}>{renderContents()}</Drawer>
In Box component api, we can pass the component data as a 'function'. like the below example.
import Drawer from "#material-ui/core/Drawer";
import Box from "#material-ui/core/Box";
const NewDrawer = ({ children, open }) => {
return (
<Box component={() => {
return <Drawer open={true}>{renderContents()}</Drawer>
}} width="300px" bgcolor="secondary.dark">
{children}
</Box>
)
}
Refer to my code sandbox example.
When using a Drawer with the temporary variant (the default), the className prop gets applied to the Modal which is not the element that you are trying to style.
Instead, you want to apply the styles from the Box to the Paper element within the Drawer. You can accomplish this using the render props approach for the Box children as shown in my example below.
import React from "react";
import Drawer from "#material-ui/core/Drawer";
import Box from "#material-ui/core/Box";
import Button from "#material-ui/core/Button";
const BoxDrawer = explicitProps => {
return (
<Box width="300px" bgcolor="secondary.dark">
{({ className }) => (
<Drawer {...explicitProps} PaperProps={{ className: className }} />
)}
</Box>
);
};
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<div className="App">
<Button variant="contained" onClick={() => setOpen(!open)}>
Open Drawer
</Button>
<BoxDrawer open={open} onClose={() => setOpen(false)}>
<div style={{ textAlign: "center" }}>
<h1>Hello CodeSandbox</h1>
<Button variant="contained" onClick={() => setOpen(!open)}>
Close Drawer
</Button>
</div>
</BoxDrawer>
</div>
);
}

Resources