Using forwardRef & useImperativeHandle to trigger child functions from parent - reactjs

I am trying to trigger two functions from a parent component by using forwardRef & useImperativeHandle. My code is below. I am not sure where I am going wrong but my code is failing to compile with the errors below. Please can someone advise? I thought i have defined the two functions in question that the compiler is complaining about. have I got the syntax wrong?
const SimpleBackdrop = React.forwardRef((props, ref) => {
const [open, setOpen] = React.useState(false);
React.useImperativeHandle(ref, () => ({
handleClose() {
setOpen(false);
},
handleToggle() {
setOpen(!open);
},
}));
return (
<div>
<Button onClick={handleToggle}>Show backdrop</Button>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={open}
onClick={handleClose}
>
<CircularProgress color="inherit" />
</Backdrop>
</div>
);
});
export default SimpleBackdrop;
I am getting the following error in the console:
Failed to compile.
[eslint]
src/components/myApp/SimpleBackdrop.js
Line 44:24: 'handleToggle' is not defined no-undef
Line 48:18: 'handleClose' is not defined no-undef
I have alos tried to re-write is as below, but still getting the same compile error:
import * as React from 'react';
import Backdrop from '#mui/material/Backdrop';
import CircularProgress from '#mui/material/CircularProgress';
import Button from '#mui/material/Button';
const SimpleBackdrop = (props, ref) => {
const [open, setOpen] = React.useState(false);
React.useImperativeHandle(ref, () => ({
handleClose: () => setOpen(false),
handleToggle: () => setOpen(!open)
}));
return (
<div>
<Button onClick={handleToggle}>Show backdrop</Button>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={open}
onClick={handleClose}
>
<CircularProgress color="inherit" />
</Backdrop>
</div>
);
}
export default React.forwardRef(SimpleBackdrop);

Try this:
const SimpleBackdrop = React.forwardRef(({...props}, ref) => {
const [open, setOpen] = React.useState(false);
const handleClose = () => {
setOpen(false);
};
const handleToggle = () => {
setOpen(!open);
};
React.useImperativeHandle(ref, () => ({
handleClose, handleToggle
}));
return (
<div>
<Button onClick={handleToggle}>Show backdrop</Button>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={open}
onClick={handleClose}
>
<CircularProgress color="inherit" />
</Backdrop>
</div>
);
});
export default SimpleBackdrop;

Related

Show component when onClick method is called

I have imported a schedular component from devexpress and have modified the appointments in this with the constant MyAppointment. Now i want to be able to delete and modify apointment data with a dialog when clicking on these. Therefore i added an onClick method to the MyAppointment const and tried to return the dialog however nothing happens when pressing the appointments.
const MyAppointment = ({ children, style, ...restProps }) => {
return (
<Appointments.Appointment
{...restProps}
style={{
...style,
backgroundColor: "#a02d37",
borderRadius: "8px",
}}
onClick={()=>{
return (
<SimpleDialogDemo />
)
}}
>
{children}
</Appointments.Appointment>
);
}
const emails = ['username#gmail.com', 'user02#gmail.com'];
function SimpleDialog(props) {
const { onClose, selectedValue, open } = props;
const handleClose = () => {
onClose(selectedValue);
};
const handleListItemClick = (value) => {
onClose(value);
};
return (
<Dialog onClose={handleClose} open={open}>
<DialogTitle>Set backup account</DialogTitle>
<List sx={{ pt: 0 }}>
{emails.map((email) => (
<ListItem button onClick={() => handleListItemClick(email)} key={email}>
<ListItemAvatar>
<Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
<ListItem autoFocus button onClick={() => handleListItemClick('addAccount')}>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Add account" />
</ListItem>
</List>
</Dialog>
);
}
SimpleDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
selectedValue: PropTypes.string.isRequired,
};
export default function SimpleDialogDemo() {
const [open, setOpen] = React.useState(true);
const [selectedValue, setSelectedValue] = React.useState(emails[1]);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = (value) => {
setOpen(false);
setSelectedValue(value);
};
handleClickOpen();
return (
<div>
<SimpleDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
/>
</div>
);
}
onClick={()=>{
return (
<SimpleDialogDemo />
)
}}
isn't actually doing anything. All you're doing is returning a component in an event handler that doesn't do anything with that return value. The onClick event handler is react-agnostic.
If you want to display a component when something is clicked, you'll need to use local state variables to show/hide it based on a click action like below:
const MyAppointment = ({ children, style, ...restProps }) => {
const [showDialog, setShowDialog] = useState(false);
return (
<Appointments.Appointment
{...restProps}
style={{
...style,
backgroundColor: "#a02d37",
borderRadius: "8px",
}}
onClick={()=> setShowDialog(true)}
>
{children}
{showDialog && <SimpleDialogDemo onClose=(() => setShowDialog(false))/>}
</Appointments.Appointment>
);
}
Note the onClose prop I added to your SimpleDialog component so that you have a way of setting the showDialog state back to hidden (you will need to add this propr if you use it).

What is the "TransitionProps" param in Material-ui?

I'm learning how to use Material-ui for ReactJS, I don't understand what is the "TransitionProps" in this example code below:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Popper from '#material-ui/core/Popper';
import Fade from '#material-ui/core/Fade';
const useStyles = makeStyles((theme) => ({
paper: {
border: '1px solid',
padding: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
},
}));
export default function TransitionsPopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'transitions-popper' : undefined;
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick}>
Toggle Popper
</button>
<Popper id={id} open={open} anchorEl={anchorEl} transition>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<div className={classes.paper}>The content of the Popper.</div>
</Fade>
)}
</Popper>
</div>
);
}
What will be passed into "TransitionProps"?
Here is the source code on Codesandbox
Hoping someone will explain some more on this syntax.
Thanks.

React Virtuoso Load More Button

I am reading the react virtuoso documentation and it has the following example.
When replicating it in, I get the error that this generateUsers function is not defined.
Its my first time using virtuoso so I am a bit lost with the docu.
Any hint is super welcome
Thanks
import { Virtuoso } from 'react-virtuoso'
import {useState, useEffect, useCallback} from 'react'
const App=()=> {
const [users, setUsers] = useState(() => [])
const [loading, setLoading] = useState(false)
const loadMore = useCallback(() => {
setLoading(true)
return setTimeout(() => {
setUsers((users) => ([...users, ...generateUsers(100, users.length)]) )
setLoading(() => false)
}, 500)
}, [setUsers, setLoading])
useEffect(() => {
const timeout = loadMore()
return () => clearTimeout(timeout)
}, [])
return (
<Virtuoso
style={{height: 300}}
data={users}
itemContent={(index, user) => { return (<div style={{ backgroundColor: user.bgColor }}>{user.name}</div>) }}
components={{
Footer: () => {
return (
<div
style={{
padding: '2rem',
display: 'flex',
justifyContent: 'center',
}}
>
<button disabled={loading} onClick={loadMore}>
{loading ? 'Loading...' : 'Press to load more'}
</button>
</div>
)
}
}}
/>
)
}
export default App

useEffect running on mount causing error and blank object

I keep getting a blank avatarlistitem when I click add for the first time and then after that the appropriate item will display after the second click. If I add a new URL and click the third time the item won't appear until the fourth click. I am most likely doing something wrong with my useEffect and state values.
import React, { useState, useEffect } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import AvatarListItem from './AvatarListItem';
import AddIcon from '#material-ui/icons/Add';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column',
flexWrap: 'wrap',
backgroundColor: theme.palette.background.paper,
},
inline: {
display: 'inline',
},
formControl: {
width: '100%',
margin: theme.spacing(1),
marginBottom: '50px',
minWidth: 120,
},
extendedIcon: {
margin: '10px',
marginRight: theme.spacing(1),
},
}));
export default function AvatarList() {
const classes = useStyles();
const [url, setUrl] = useState('');
const [itemDetails, setItemDetails] = useState({});
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:5000/api/resources/products?url=${url}`,
)
setItemDetails(result.data[0]);
}
fetchData()
}, [search])
const addItem = (itemDetails) => {
const newItems = [...items, itemDetails];
setItems(newItems);
};
let handleSubmit = (e) => {
e.preventDefault();
console.log(itemDetails);
addItem(itemDetails);
};
let removeItem = (index) => {
const newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
return (
<div>
<form className={classes.formControl} onSubmit={e => handleSubmit(e)}>
<TextField id="outlined-basic" label="Amazon Url" variant="outlined" name='newItem' onChange={e => setUrl(e.target.value)} value={url} />
<Button variant="contained" color="primary" type="submit" value="Submit" onClick={() => setSearch(url)}>
<AddIcon className={classes.extendedIcon} />
</Button>
</form>
<List className={classes.root}>
{items.map((item, index) => (
<>
<AvatarListItem
itemDetails={item}
key={index}
index={index}
removeItem={removeItem}
/>
<Divider variant="middle" component="li" />
</>
))}
</List>
</div >
);
}
A better approach may be to purely rely on the onSubmit callback instead of relying on the useEffect which may run more often than needed. Also, it doesn't look like you need to use the search or itemDetails state at all.
import React, { useState, useEffect } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import AvatarListItem from './AvatarListItem';
import AddIcon from '#material-ui/icons/Add';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column',
flexWrap: 'wrap',
backgroundColor: theme.palette.background.paper,
},
inline: {
display: 'inline',
},
formControl: {
width: '100%',
margin: theme.spacing(1),
marginBottom: '50px',
minWidth: 120,
},
extendedIcon: {
margin: '10px',
marginRight: theme.spacing(1),
},
}));
export default function AvatarList() {
const classes = useStyles();
const [url, setUrl] = useState('');
const [items, setItems] = useState([]);
const addItem = (itemDetails) => {
const newItems = [...items, itemDetails];
setItems(newItems);
};
let handleSubmit = async (e) => {
e.preventDefault();
const result = await axios(
`http://127.0.0.1:5000/api/resources/products?url=${url}`,
)
addItem(result.data[0]);
};
let removeItem = (index) => {
const newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
return (
<div>
<form className={classes.formControl} onSubmit={handleSubmit}>
<TextField id="outlined-basic" label="Amazon Url" variant="outlined" name='newItem' onChange={e => setUrl(e.target.value)} value={url} />
<Button variant="contained" color="primary" type="submit">
<AddIcon className={classes.extendedIcon} />
</Button>
</form>
<List className={classes.root}>
{items.map((item, index) => (
<React.Fragment key={index}>
<AvatarListItem
itemDetails={item}
key={index}
index={index}
removeItem={removeItem}
/>
<Divider variant="middle" component="li" />
</React.Fragment>
))}
</List>
</div >
);
}
if your intention is fetching data every time user click submit, based on your useEffect, you should not base your useEffect on search but on items itself (as when user submit, you add something to items)
your code should look like this
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:5000/api/resources/products?url=${url}`,
)
setItemDetails(result.data[0]);
}
fetchData()
}, [items])

how to type transition props in material ui popper

I am using material ui popper and want to separate the transition into a separate function as follows
import React from 'react';
import { makeStyles, Theme, createStyles } from '#material-ui/core/styles';
import Popper from '#material-ui/core/Popper';
import Fade from '#material-ui/core/Fade';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
paper: {
border: '1px solid',
padding: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
},
}),
);
export default function TransitionsPopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'transitions-popper' : undefined;
const popperTrans = ({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<div className={classes.paper}>The content of the Popper.</div>
</Fade>
)
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick}>
Toggle Popper
</button>
<Popper id={id} open={open} anchorEl={anchorEl} transition>
{popperTrans}
</Popper>
</div>
);
}
I am using typescript and it's throwing tslint error Binding element 'TransitionProps' implicitly has an 'any' type. How can I type TransitionProps here?
Alternatively, TransitionProps is exported, so you can do:
import { TransitionProps as TransitionPropsType } from "#material-ui/core";
Then:
const popperTrans = ({ TransitionProps }: { TransitionProps: TransitionPropsType }) => (
<Fade {...TransitionProps} timeout={350}>
<div className={classes.paper}>The content of the Popper.</div>
</Fade>
)
I found the answer. We can import FadeProps from Fade and use that to type TransitionProps.
import React from 'react';
import { makeStyles, Theme, createStyles } from '#material-ui/core/styles';
import Popper from '#material-ui/core/Popper';
import Fade, { FadeProps } from '#material-ui/core/Fade';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
paper: {
border: '1px solid',
padding: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
},
}),
);
export default function TransitionsPopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'transitions-popper' : undefined;
const popperTrans = ({ TransitionProps }: { TransitionProps: FadeProps }) => (
<Fade {...TransitionProps} timeout={350}>
<div className={classes.paper}>The content of the Popper.</div>
</Fade>
)
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick}>
Toggle Popper
</button>
<Popper id={id} open={open} anchorEl={anchorEl} transition>
{popperTrans}
</Popper>
</div>
);
}

Resources