React - Snackbar as global component (Typescript) - reactjs

Merry Christmas at first to all of you!
[React + TypeScript]
And yes, I'm a newbie in react, I'm more kind of backend geek, but we all need to learn new stuff :)
I am trying to make my snackbar work in all components globally, to not write a new one every time.
I saw one post about this before: How to implement material-ui Snackbar as a global function?
But unfortunatelly I can't make it work.
Is my function even correct?
And if it is, how can I call it from another component now?
I made this function:
SnackbarHOC.tsx
`
import { AlertTitle, IconButton, Snackbar } from '#mui/material';
import Slide from '#mui/material';
import Alert from '#mui/material';
import { useState } from 'react';
import CloseIcon from '#mui/icons-material/Close';
function SnackbarHOC<T>(WrappedComponent: React.ComponentType<T>, Alert: React.ElementType) {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState("I'm a custom snackbar");
const [duration, setDuration] = useState(2000);
const [severity, setSeverity] = useState(
"success"
); /** error | warning | info */
return (props: T) => {
const showMessage = (message: string, severity = "success", duration = 2000) => {
setMessage(message);
setSeverity(severity);
setDuration(duration);
setOpen(true);
};
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
}
return (
<>
<WrappedComponent {...props} snackbarShowMessage={showMessage} />
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
autoHideDuration={duration}
open={open}
onClose={handleClose}
//TransitionComponent={Slide}
>
<Alert severity="error"
sx={{ width: '100%' }}
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={handleClose}>
<CloseIcon fontSize="inherit" />
</IconButton>
}>
{message}
</Alert>
</Snackbar>
</>
);
};
};
export default SnackbarHOC;
`
I tried to call it from another component, but I have no idea how to show the actual snackbar now :(.
It's all that is not giving me errors right now:
`
import SnackbarHOC from '../../SnackbarHOC';
`

I found a solution for that but in JS basically, it's the same I guess it's just not typed. I'm implementing within NEXTJS latest
//index.jsx
import Head from 'next/head'
import { useState } from 'react';
import {Button} from '#mui/material'
import Notification from '../components/Notification.component'
//create the jsx file with the Snackbar component implemented
function Home() {
// we need a simple hook for the button
const [open, setOpen] = useState(false);
}
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return(
<div >
<Head>
<title>HOME</title>
<meta name="description" content="AWESOME" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Button variant="outlined" onClick={handleClick}>SEND</Button>
{/*We can define new props for our component I'm mixin props from mui Alert and Snackbar, from Alert I'm using severity and from Snackbar the open and onClose and message goes inside the alert as a simple string :D */}
<Notification
open={open}
onClose={handleClose}
severity="error"
message="that is what I am talking about"/>
</div>
)
}
Now we just need to create the custom component that has multiple lil components inside that we can edit as we want!
//Notification.component.jsx file
import React, { forwardRef} from 'react';
import Snackbar from '#mui/material/Snackbar';
import MuiAlert from '#mui/material/Alert';
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
const Notification= ({open, onClose, severity, message}) => {
/*<> ... </> The empty tag is shorthand for <React. Fragment>, allowing you to have multiple top-most elements without wrapping further HTML.
*/
return (
<>
<Snackbar open={open} autoHideDuration={6000} onClose={onClose}>
<Alert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
</>
);
}
export default Notification;
But if you really want use typed values You can check the returned type in your VSCode for each variable and function. but I really don't see any benefit for that. But as it doesn't change the result I will not implement it for me neither here.
I highly recommend to watch this video series https://www.youtube.com/watch?v=Roxf91mp3kw&t=367s

Related

React Component hook variable will not update

I have a React component that gets data from a parent page/component. I use this data object (jobData) to populate a hook variable value when the component (a modal) is fired. The jobData looks like this: [{id: '17003', file_name: 'My_File', type: 'Medium', state: 'Arkansas'}]. In the browser debugger, I can see the jobData getting passed into the component, but when it gets to return ....<TextField .... value={productID} the productID says undefined! Any suggestions as to what I am doing wrong? I want the TextField to display the value of jobData[0]['id'] when it fires and then store the value of productID when it cahnges.
import React, { useState, useEffect } from 'react';
import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, Modal,
FormControl, Select, InputLabel } from '#mui/material';
import { createTheme, ThemeProvider } from '#mui/material/styles';
export default function ScheduleProdsModal({jobData}) {
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(jobData[0]["id"]);
return (
<div>
<ThemeProvider theme={theme}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleOpen}>Schedule Products</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Schedule Products</DialogTitle>
<DialogContent >
<FormControl fullWidth style={{marginTop: '5px', marginBottom: '5px'}}>
<TextField
autoFocus
margin="dense"
width="100%"
id="my_id"
label="My ID"
type="text"
value={productID}
variant="outlined"
onChange={(e) => {
setProductID(e.target.value);
}}
/>
</FormControl>
</DialogContent>
<DialogActions style={{marginRight: 'inherit'}}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleClose}>Close</Button>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleScheduler}>Schedule</Button>
</DialogActions>
</Dialog>
</ThemeProvider>
</div>
);
}
It seems like you have set jobData array when initialising the projectId, which will be undefined in the first load unless it has been handled in the parent component. Therefore as a workaround we normally use useEffect hook to get the upcoming props changes. So, when the jobData array is not null, you can use setProductID to get the relevant product id inside the useEffect hook. For the time period that you are not getting the data, you can use your favourite loading mechanism to show a loading screen. Find the below code snippet.
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(0);
useEffect(() => {
if(jobData && jobData.length > 0){
setProductID(Number(jobData[0]["id"]))
}
}, [jobData])
// You can put more conditions like undefined and null checking if you want
if(productID === 0){
// Use your fav loading page or loader here
return <>Loading...</>
}
return (
<div>
<ThemeProvider theme={theme}>
Hope this would help.

How to check if my react-native-modalize modal is open or close?

Actually i am working on multiple modal at once so i have to make a condition that if my modal with "xyz" ref is close then display a button.
react-native-modalize has onOpen and onClose props which will allow you to set some state to keep track of then open/closed status of each modal. You can then use that state to control the rendering of your buttons.
Example on Expo Snack.
import React, { useRef } from 'react';
import { View, Text, Button } from 'react-native';
import { Modalize } from 'react-native-modalize';
import RnGH from 'react-native-gesture-handler';
const { useState, useEffect } = React;
export default function App() {
const modalA = useRef(null);
const modalB = useRef(null);
const [modalAOpen, setModalAOpen] = useState(false);
const [modalBOpen, setModalBOpen] = useState(false);
return (
<>
<Button
onPress={() => {
if (modalAOpen) {
modalA.current?.close();
} else {
modalA.current?.open();
}
}}
title={`${modalAOpen ? 'Close' : 'Open'} Modal A`}
/>
<Button
onPress={() => {
if (modalBOpen) {
modalB.current?.close();
} else {
modalB.current?.open();
}
}}
title={`${modalBOpen ? 'Close' : 'Open'} Modal B`}
/>
<Modalize
ref={modalA}
onOpen={() => setModalAOpen(true)}
onClose={() => setModalAOpen(false)}>
<Text style={{ color: 'red' }}>Modal A</Text>
</Modalize>
<Modalize
ref={modalB}
onOpen={() => setModalBOpen(true)}
onClose={() => setModalBOpen(false)}>
<Text style={{ color: 'blue' }}>Modal B</Text>
</Modalize>
</>
);
}
You should make a custom component with a modal in it, so every modal has its own state - isOpen. Then in your custom modal component, you can show something or not, based on your isOpen property.

How to display elements in JSX based on OnChange of react-select element

I am new to react and self-taught, struggling with state and react-select
I have a dropdown with react-select. Depending on what value the user selects I want to display the relevant data onto the screen. The data is coming from the authContext(useContext). This is what I have written so far. But its not working. Can someone please guide me in the right direction:
import React, { useContext, useState } from 'react'
import styles from './FullRecord.module.css'
import {AuthContext} from '../../shared/context/auth-context'
import Select from 'react-select'
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles({
custom: {
backgroundColor: "#558d6b",
fontWeight: "bold"
},
customFont: {
fontWeight: "bold",
fontSize: "20px"
},
customFont1: {
fontWeight: "light"
}
});
const FullRecord = (props) => {
const auth = useContext(AuthContext)
const classes = useStyles();
const [selectedValue, setSelectedValue] = useState('');
const [tableValue, setTableValue] = useState(false)
let data
const options = auth.tournaments.map((tournament) => {
return {
value: tournament.TournamentName,
label: tournament.TournamentName,
}
})
const handleChange = (selectedValue) => {
setSelectedValue(selectedValue);
setTableValue(true)
const {value} = selectedValue
let tname
if(value === 'GSM Edition 1'){
const noOfMatches = auth.profile.MemberMatches.filter((match) => match.TournamentName === 'GSM Edition 1')
if(tableValue){
return (
<div>
<li className={styles['member-item']}>
<Card className={classes.custom} variant="outlined">
<CardContent>
<Typography className={classes.customFont} gutterBottom>
Number Of Matches Played
</Typography>
<Typography className={classes.customFont}>
{noOfMatches}
</Typography>
</CardContent>
</Card>
</li>
</div>
)
}
}
}
return (
<React.Fragment>
<div className={styles['fullrecord__maindiv']}>
<Select
onChange={handleChange}
options={options}
/>
</div>
</React.Fragment>
)
}
export default FullRecord
I would say that your first problem stems from using a function argument name that is the same as your state variable. Especially a problem as the function is also an arrow function.
const handleChange = (newValue) => {
setSelectedValue(newValue);
setTableValue(true);
}
You're also trying to return JSX from your event handler, where it seems like what you really want is the core content to change when your selection changes. Ultimately, that logic goes in to your FullRecord return statement, and will automatically update as state is updated.
const FullRecord = (props) => {
// ...bunch of stuff, then
return (
<React.Fragment> {/* really don't need here, as you only have one root element */}
<div className={styles['fullrecord__maindiv']}>
<Select
onChange={handleChange}
options={options}
/>
{selectedValue ? (
{/* output something here */}
) : null}
</div>
</React.Fragment>
)
}

Snackbar can't give width and handleClose

I can't give 100% width on the snackbar and I also have a snackbarClose method but I can't implement it on the snackbar. I also want a button 'X' which button will perform the snackbarClose method.
CodeSandbox : https://codesandbox.io/s/xenodochial-kapitsa-f5yd7?file=/src/Demo.js:693-706
import React, { Component } from "react";
import { Container, Grid, Button, Snackbar } from "#material-ui/core";
import MuiAlert from "#material-ui/lab/Alert";
import { withStyles } from "#material-ui/core/styles";
const styles = (theme) => ({});
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
class Demo extends Component {
constructor() {
super();
this.state = {
snackbaropen: false,
snackbarmsg: "",
severity: ""
};
this.onClick = this.onClick.bind(this);
}
onClick = (event) => {
this.setState({
snackbaropen: true,
snackbarmsg: "Data Saved",
severity: "success"
});
};
snackbarClose = (event) => {
this.setState({ snackbaropen: false });
};
render() {
const { classes } = this.props;
return (
<React.Fragment>
<Container>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={6} lg={6} xl={6}>
<Button
variant="contained"
color="primary"
onClick={this.onClick}
>
Submit
</Button>
<Snackbar
open={this.state.snackbaropen}
autoHideDuration={3000}
onClose={this.snackbarClose}
>
<Alert severity={this.state.severity}>
{this.state.snackbarmsg}
</Alert>
</Snackbar>
</Grid>
</Grid>
</Container>
</React.Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(Demo);
If you want 100% width on snackbar, you need to specify width for Alert and Snackbar component and for close button you need to specify onClose function on Alert component.
<Snackbar
open={this.state.snackbaropen}
autoHideDuration={3000}
onClose={this.snackbarClose}
style={{ width: "100%" }} // specify width 100%
>
<Alert
onClose={this.snackbarClose} // specify onClose method
severity={this.state.severity}
style={{ width: "100%" }} // specify width 100%
>
{this.state.snackbarmsg}
</Alert>
</Snackbar>
For snackback closing on outside click, you need to change close function like below:-
snackbarClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ snackbaropen: false });
};
Demo: https://codesandbox.io/s/heuristic-khayyam-xo464
This is so complicated to solve problem.
What React version used?
If you used React(over 16.8), I recommend solution I used.
Use React.createContext, React.useContext
Use React.useReducer
Make custom Hook like useSnackBar
Mount SnackBarProvider on the App
import React from 'react';
import ToastContext, { ToastProps } from './ToastContext';
const useToast = () => {
const [, dispatch] = React.useContext(ToastContext);
const message = (toast: ToastProps) => {
dispatch({
type: 'ADD_TOAST',
payload: toast,
});
};
return message;
};
export { useToast };
export default useToast;
const ToastProvider: React.FC<ToastProviderProps> = ({
children,
placement = 'top-right',
timeout = 5000,
}) => {
const [toasts, dispatch] = React.useReducer(toastReducer, []);
return (
<ToastContext.Provider value={[toasts, dispatch]}>
<ToastProviderWrapper>
<ToastProviderContainer className={classnames(placement)}>
{toasts.map((toast, index) => (
<ToastCard {...toast} key={index} timeout={timeout} />
))}
</ToastProviderContainer>
</ToastProviderWrapper>
{children}
</ToastContext.Provider>
);
};
const Index = () => (
<ToastProvider>
<App />
</ToastProvider>
);
const rootElement = document.getElementById('root');
ReactDOM.render(<Index />, rootElement);
You can give data through dispatch(action), reducers
You can Dispatch event all the pages.
These Doms were rendered Root Element like React.Portal. then you can edit global position styles as system.
The SnackBar component delegates to or inherits its style or appearance from its children component, so you can instead adjust the width of the Alert component inside the SnackBar.
<Alert style={{ width: "100%" }} severity={this.state.severity}>
{this.state.snackbarmsg}
</Alert>
This should also adjust the width of the SnackBar component and make it fullwidth.

React passing down a function that modifies a third component

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>
);
}

Resources