Material UI Snackback reloads the page in Functional component - reactjs

Why changing the state here in the codesandbox here do not reloads/render/paint the page. I have also added comments where i am not understanding
https://codesandbox.io/s/material-demo-nb5ek?file=/demo.js
I did same thing in my case according to the material UI snackbar documentation: The only difference is that i pass the click function to child and invoke from there.
I have created a material UI snackbar in the parent which is a functional component and pass the notification function in Route shown below to the child which is also a functional component.
const [openNotification, setOpenNotification] = React.useState(false);
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpenNotification(false);
};
const notification = (status) => {
setOpenNotification(status)
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route exact {...rest} render={(props) => (
<Component {...props} notification={notification}/>
)} />
)
This is the HTML where i am using it in the parent.
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={openNotification}
autoHideDuration={6000}
onClose={handleClose}
message='Success'
action={
<React.Fragment>
<IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
}
/>
In child i have used the notification function like this in the submit function.
function handleSubmit() {
props.notification(true)
}
Problem: The page renders when the snackbar closes and i dont want it to happen as it just a notification. I know snackbar have a state related to it which is changing from true to false. Although the example over here do not renders the page again from their documentation sandbox when the snackbar closes: https://codesandbox.io/s/5tgvq.
The link to simple snackbar documentation: https://material-ui.com/components/snackbars/
I tried not to use state for it and just make a variable and update it. But then the snackbar wont open even if the value gets true

Related

node.current shows up as null when using useRef(), despite using within useEffect hook

My goal is to large scrollable modal where if the item provided to the modal changes (there is a "More projects" section at the bottom which should change the modal content), the modal automatically scrolls to the top. Since I can't use the window object, other sources seem to indicate a ref is the best way to go. However, I keep getting the error node.current is undefined at the time of compiling.
I saw elsewhere that this should be avoidable by working with the ref within a useEffect hook, since this will ensure the ref has been initialized by the time it runs, however this is not happening here.
const PortfolioModal = ({
open,
handleClose,
item,
setItem,
...props
}) => {
const node = useRef();
useEffect(() => {
node.current.scrollIntoView()
}, [item]);
return (
<Dialog onClose={handleClose} open={open} fullWidth={true} maxWidth='lg'>
<div ref={node}></div>
<Content>
{a bunch of stuff is here}
<PortfolioFooter
backgroundImage={`${process.env.PUBLIC_URL}/images/backgrounds/panel5.png`}
item={item}
setItem={setItem}
/>
</FlexContainer>
</Content>
</Dialog>
)
};
EDIT: Additional note -- I initially wrapped the entire component with a div with a ref and tried to use scrollTop and I did not receive an error, but there was also no scrolling, so I thought I would try using scrollIntoView with an invisible element.
This is what worked for me, based on this post:
const nodeRef = useRef();
useEffect(() => {
if (nodeRef.current) {
nodeRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}, [item]);
return (
<Dialog onClose={handleClose} open={open} fullWidth={true} maxWidth='lg' scroll="body">
<div ref={node => {
nodeRef.current = node;
}}>
<Content>
{content}
</Content>
</div>
</Dialog>

how to update state to wait for a function to complete in React

I am developing a React functional component for a model CRUD operations, this component will render save and delete buttons on the model form, and I am trying to show a waiting indicator when the user clicks the save or delete button and hide the indicator when the process completes.
I am using the material-ui React components library, and for the waiting indicator I am using the Backdrop component.
the component props are the save and delete callbacks and set by the parent component.
I added a boolean state to show/hide this backdrop, but the waiting indicator is not showing as the setState in react is asynchronous. so how can I achieve this?
here is my component:
export default function ModelControls({onSave, onDelete}) {
const [wait, setWait] = useState(false);
const saveClick = () => {
setWait(true);
const retId = onSave();
setWait(false);
...
};
return (
<Container maxWidth={"xl"}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Box display="flex" justifyContent="flex-end">
<Box component="span">
<Button size="small" color="primary" onClick={saveClick}>
<SaveIcon />
</Button>
</Box>
</Box>
</Grid>
</Grid>
<Backdrop open={wait}>
<CircularProgress color="primary" />
</Backdrop>
</Container>
);
}
Just make the function async and add await in front of the save function.
const saveClick = async () => {
setWait(true);
const retId = await onSave();
setWait(false);
};
Thanks #Dipansh, you inspired me to the following solution.
now the onSave callback from parent must return a promise object
const saveClick = () => {
setWait(true);
onSave().then((retId) => {
...
setWait(false);
});
};
this way it is working as needed.

Problem with reseting openKeys in Ant Design Menu within Sider

I have antd Menu inside collapsible Sider. I've set default open keys for one of Submenus and they should open one at a time. Here is my code for the Menu:
const MainMenu = ({ defaultOpenKeys }) => {
const [openKeys, setOpenKeys] = useState(defaultOpenKeys);
const rootKeys = ["sub1", "sub2"];
// Open only one submenu at a time
const onOpenChange = props => {
const latestOpenKey = props.find(key => openKeys.indexOf(key) === -1);
if (rootKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(props);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : defaultOpenKeys);
}
};
return (
<Menu
theme="dark"
openKeys={openKeys}
defaultSelectedKeys={["1"]}
mode="inline"
onOpenChange={onOpenChange}
>
<Menu.Item key="1">
Option 1
</Menu.Item>
<SubMenu key="sub1" title="User">
<Menu.Item key="2">Tom</Menu.Item>
</SubMenu>
<SubMenu key="sub2" title="Team">
<Menu.Item key="3">Team 1</Menu.Item>
</SubMenu>
</Menu>
);
};
export default MainMenu;
I pass defaultOpenKeys from the Sider.
const SiderDemo = () => {
const [collapsed, setCollapsed] = useState(false);
const toggleSider = () => {
setCollapsed(!collapsed);
};
return (
<Layout style={{ minHeight: "100vh" }}>
<Button type="primary" onClick={toggleSider}>
{React.createElement(
collapsed ? MenuFoldOutlined : MenuUnfoldOutlined
)}
</Button>
<Sider
collapsible
collapsed={collapsed}
collapsedWidth={0}
trigger={null}
>
<Menu defaultOpenKeys={["sub1"]} />
</Sider>
...
</Layout>
);
};
It works on mount, but when I collapse the Sider, defaultOpenKeys are being reset. How can I keep defaultOpenKeys from being reset, when the Sider is collapsed?
I have created a codesandbox and added console log in the Menu. You can see that defaultOpenKeys and openKeys are the same on mount. When I collapse the Sider, the console log is triggered twice. The first time defaultOpenKeys and openKeys are the same. And the second time openKeys become empty. How can I fix that?
Reason: on closing the sidebar it is closing opened sidemenu so it gonna trigger openchange with empty array and hence your logic making it reset to empty.
Here is code sandbox link with updated code
https://codesandbox.io/s/sider-demo-0der5?file=/Menu.jsx
Suggestion: Its anti pattern to assign props to initial state. if prop value changed in parent component then the new prop value will never be displayed because intial state will never update the current state of the component. The initialization of state from props only runs when the component is first created

best approach to set bottomNavigation status from the current Active Link according to the entered path (Reach Router - Material UI)

I am using react-router and material-ui. I have a BottomNavigation and BottomNavigationAction (MaterialUI) (with Link component from Reach Roouter ).
import { Link } from "#reach/router"
const useStyles = makeStyles((theme) => ({
myButtomNavigation: {
color: '#000',
'&$selected': {
paddingTop: '16px',
},
},
selected: {
},
}));
export default function Navigation(props) {
const [value, setValue] = React.useState('INIT');
const classes = useStyles();
const handleChange = (event, newValue) => {
setValue(newValue);
};
return(
<BottomNavigation value={value} onChange={handleChange}>
<BottomNavigationAction showLabel={false} value="INIT" icon={<HomeOutlinedIcon />} component={Link} to="/" classes={{root: classes.myButtomNavigation, selected: classes.selected}} />
<BottomNavigationAction showLabel={false} value="ANIMALS" icon={<PetsOutlinedIcon />} component={Link} to="/animals" classes={{root: classes.myButtomNavigation, selected: classes.selected}}/>
<BottomNavigationAction showLabel={false} value="ROUTINE" icon={<NotificationsOutlinedIcon />} component={Link} to="/activities" classes={{root: classes.myButtomNavigation, selected: classes.selected}} />
<BottomNavigationAction showLabel={false} value="SETTINGS" icon={<SettingsOutlinedIcon />} component={Link} to="/configuration" classes={{root: classes.myButtomNavigation, selected: classes.selected}} />
</BottomNavigation>
);
}
I have a problem to know how to get the current active Link and set it to the value of the BottomNavigation when someone entered a different url than the root path from the browser.
When i enter with the root path (localhost:3000/): all is work (because i set value to root path).
When i enter with other path, like localhost:3000/animals i need to set the correct value so the right icon show active.
Reach Router gives a possiblity in Link component: passing a function to getProps that you can return a style for the current active link but the style affect anchor element and i need to affect an other object (https://reach.tech/router/example/active-links).
How can i set the value of bottomNavigation according to the current active link when someone entered path from browser (and is not the rootpath)??
In react-router-dom I am using NavLink with property (activeClassName="current") instead of Link, maybe there is something similar in Reach Router.

Why function call inside arrow function passed to a onClick in react is not working?

I am have a material-ui component AppBar (using hooks) and i have to toggle the drawer (class based component) and i have a reference to function toggleDrawer in AppBar.js using props.toggleDrawer.
I want to call the function when a user clicks on the IconButton component in AppBar so i tried to give the onClick attribute to that component with a arrow fuction having a call to toggleDrawer(), but it is not working i have to call the function directly from the onClick attribute in order to work. Why is it so, according to my knowledge we should be passing the arrow function in order to avoid the call to that function during parsing JSX to vanilla js.
<IconButton
onClick = { props.toggleDrawer('left', true) }
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="open drawer"
>
<MenuIcon />
</IconButton>
This above code is working but below is not. :(
<IconButton
onClick = { () => { props.toggleDrawer('left', true)} }
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="open drawer"
>
<MenuIcon />
</IconButton>
*note: I don't have knowledge about hooks but the AppBar component which is having the IconButton component is using hooks can it cause this issue.
If you were referring to this example drawer. Then toggleDrawer is a curried function (a function returning another function when called ones). toggleDrawer is already returning a function which will be invoked onClick. But when you put it inside an arrow function, then there is no function invocation happening. When toggleDrawer is called with side and open, it returns another function, that takes event and returns void. Check below.
const toggleDrawer = (side: DrawerSide, open: boolean) => (
event: React.KeyboardEvent | React.MouseEvent,
) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Tab' ||
(event as React.KeyboardEvent).key === 'Shift')
) {
return;
}
setState({ ...state, [side]: open });
};

Resources