Dialog of material UI has afterimage when being closed - javascript

Introduction
Bascially <Dialog /> receives open and onClose as props. open is boolean from state and onClose is a function that changes the state.
I made <CustomModal /> that wraps <Dialog />, which receives another prop content that defines what to display on <Dialog />.
// CustomModal.jsx
const CustomModal = props => (
<Dialog {...props} sx={{ '& .MuiDialog-paper': { padding: '2em' } }}>
{props.content}
</Dialog>
);
And I'm delivering handlers using context so that my modal could be open and closed everywhere.
// modalHandlersContext.js
const initialState = {
open: false,
content: null,
};
const ModalHandlersProvider = ({ children }) => {
const [modal, setModal] = useState(initialState);
const handlers = {
openModal: payload => setModal({ open: true, ...payload }),
closeModal: () => setModal(initialState),
};
return (
<ModalHandlersContext.Provider value={handlers}>
{children}
<CustomModal
open={modal.open}
onClose={handlers.closeModal}
content={modal.content}
></CustomModal>
</ModalHandlersContext.Provider>
);
};
When I want to open modal somewhere, I execute a function like this
const onLogin = () =>
openModal({
content: <h1>component</h1>,
});
That is, I pass a component. (I used h1 for a simple example)
Main subject
But when I close it, it's not clean.
I experimented on this to check when this happens.
It happens
With css, display something from props(same code as above)
const CustomModal = props => (
<Dialog {...props} sx={{ '& .MuiDialog-paper': { padding: '2em' } }}>
{props.content}
</Dialog>
);
It doesn't happen
Without css, display something from props
const CustomModal = props => (
<Dialog {...props}>
{props.content}
</Dialog>
);
2,3. With/Without css, display just plain text
const CustomModal = props => (
<Dialog {...props} sx={{ '& .MuiDialog-paper': { padding: '2em' } }}>
content
</Dialog>
);
const CustomModal = props => (
<Dialog {...props}>
content
</Dialog>
);
So after that, I tried using <DialogContent /> instead of css but It didn't work. But I have tried using <Modal /> instead of <Dialog /> and it hasn't caused any problems.
I wanna use <Dialog /> if possible but now can't find the cause.
Can anyone help?

Related

React Material Ui Dialog not Displaying Correct Values

I am trying to play with react material ui dialog boxes and I noticed a problem or maybe I am doing it wrong. I've an object a and when I click on the a button in list, it should display the respective id number but it is always displaying the id number of the last id,index instead, what is the issue? Is it because i am calling them in a loop and all three dialogue boxes are being called at the same time? what should I do to basically show the respective id with every button.
...
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const a = [{ id: 1 }, { id: 2 }, { id: 3 }];
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<List>
{a.map(({ id }, index) => {
return (
<>
<ListItem button onClick={handleClickOpen}>
{id}
</ListItem>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{id}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description" />
</DialogContent>
</Dialog>
</>
);
})}
</List>
</>
);
}
...
my sample https://codesandbox.io/s/material-demo-k5s8k?file=/demo.js
All 3 dialogs are being opened, because you are controlling all 3 of them using the same open variable. The last dialog is just the one on top. If you look at the DOM via the browser developer tools you will see that all 3 are there.
You can fix this by managing the open state in a way that allows you to tell which id is open.
One way is to set into state the id of the dialog that is open:
import React from "react";
import Dialog from "#material-ui/core/Dialog";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import DialogTitle from "#material-ui/core/DialogTitle";
import { List, ListItem } from "#material-ui/core";
export default function AlertDialog() {
const [openId, setOpenId] = React.useState(null);
const a = [{ id: 1 }, { id: 2 }, { id: 3 }];
const handleClickOpen = id => {
setOpenId(id);
};
const handleClose = () => {
setOpenId(null);
};
return (
<>
<List>
{a.map(({ id }, index) => {
return (
<>
<ListItem button onClick={() => handleClickOpen(id)}>
{id}
</ListItem>
<Dialog
open={openId === id}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{id}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description" />
</DialogContent>
</Dialog>
</>
);
})}
</List>
</>
);
}

React how to fix stale state in child component

Creating multiple child components, which each can setState of its parent, makes these child components have different versions of history of the state (aka stale state)
To reproduce the error:
create 2 child components by clicking "add new social media"
submit both child components to set parent state
submit the firstly created component again, and now the second component input disappears from the resulting state
Another error:
create 2 child components by clicking "add new social media"
submit the secondly created child component
submit the firstly created child component, and now the second component input disappears from the resulting state
All in all, I want the resulting state to have ALL the data of each components. How can I fix this?
https://codesandbox.io/s/blissful-fog-oz10p
const Admin = () => {
const [links, setLinks] = useState({});
const [newLink, setNewLink] = useState([]);
const updateLinks = (socialMedia, url) => {
setLinks({
...links,
[socialMedia]: url
});
};
const linkData = {
links,
updateLinks
};
const applyChanges = () => {
console.log(links);
};
return (
<>
{newLink ? newLink.map(child => child) : null}
<div className="container-sm">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={() => {
setNewLink([
...newLink,
<AddNewLink key={Math.random()} linkData={linkData} />
]);
}}
>
Add new social media
</Button>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
style={{ marginTop: "50px" }}
onClick={() => applyChanges()}
>
Apply Changes
</Button>
<h3>{JSON.stringify(links, null, 4)}</h3>
</div>
</>
);
};
export default Admin;
const AddNewLink = props => {
const [socialMedia, setSocialMedia] = useState("");
const [url, setUrl] = useState("");
const { updateLinks } = props.linkData;
const handleSubmit = () => {
updateLinks(socialMedia, url);
};
return (
<>
<FormControl
style={{ marginTop: "30px", marginLeft: "35px", width: "90%" }}
>
<InputLabel>Select Social Media</InputLabel>
<Select
value={socialMedia}
onChange={e => {
setSocialMedia(e.target.value);
}}
>
<MenuItem value={"facebook"}>Facebook</MenuItem>
<MenuItem value={"instagram"}>Instagram</MenuItem>
<MenuItem value={"tiktok"}>TikTok</MenuItem>
</Select>
</FormControl>
<form
noValidate
autoComplete="off"
style={{ marginBottom: "30px", marginLeft: "35px" }}
>
<TextField
id="standard-basic"
label="Enter link"
style={{ width: "95%" }}
onChange={e => {
setUrl(e.target.value);
}}
/>
</form>
<div className="container-sm">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
style={{ marginBottom: "30px" }}
onClick={() => handleSubmit()}
>
Submit
</Button>
</div>
</>
);
};
export default AddNewLink;
The problem you are facing is caused by updateLinks() function closing over the links state.
When you create two AddNewLink components, each of them is passed updateLinks() function. Since initially, links is an empty object, updateLinks() function passed to both instances of AddNewLink, has a closure over the links variable and links at that time refers to an empty object {}. So when you submit the form, as far as updateLinks() function is concerned, links is an empty object. So when merging links with the data passed from AddNewLink component, only the data from the submitted form is saved in the state because links is an empty object.
Solution:
You could use useRef hook to access the latest value of links inside updateLinks() function.
const Admin = () => {
...
const linkRef = useRef();
const updateLinks = (socialMedia, url) => {
linkRef.current = { ...linkRef.current, [socialMedia]: url };
setLinks(linkRef.current);
};
...
};
Also i don't think you need more than one instance of AddNewLink component. You could add more than one social media link in the state with only a single instance of AddNewLink component.
Demo:

Modal is not Closing ReactJS Hooks Using Parent and Child Components

I am opening Model (child Component) on Button Click from Parent Component, it opens very well but its not closing and it shows some error:
Uncaught TypeError: setOpen is not a function from Child Component
Here is My Parent Component
<TableCell>
<Button
variant="contained"
size="small"
color="primary"
onClick={() => deleteHandler(index)}
>
Delete Me
</Button>
</TableCell>
{console.log(open)}
{open && <AddList open={open} setOpen={open} />}
My Child Component
export default function TransitionsModal(open, setOpen) {
const classes = useStyles();
// const [openL, setOpenL] = React.useState(null);
// const handleOpen = () => {
// setOpen(true);
// };
const handleClose = () => {
setOpen(!open);
};
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">Transition modal</h2>
<p id="transition-modal-description">
react-transition-group animates me.
</p>
</div>
</Fade>
</Modal>
</div>
);
}
Your first issue is that you are passing a Boolean for the setOpen prop rather than the setOpen function itself, so change it to setOpen={setOpen}.
// RenderList.js
const RenderList = props => {
// ...
return (
...
{open && <AddList open={open} setOpen={setOpen} />}
)
}
Your second issue is that you're not destructing props properly in the TransitionsModal component. Use {} to destruct the props object and grab what you need.
// AddList.js
export default function TransitionsModal({ open, setOpen }) {
// ...
}
Here's the fixed example:
CodeSandbox
Hope this helps.
Hi take a look at this
https://codesandbox.io/s/frosty-bird-5yh5g
in RenderList.js you didn't pass setOpen
{open && <AddList open={open} setOpen={setOpen} />}
also export default function TransitionsModal({ open, setOpen }) {

Expected an assignment or function call and instead saw an expression react router

I have button in a Material Table. I am using react routers to route pages to different URLs.
This page is supposed to set up functions and call the Material Table <MuiTable> and then render a button below the material table. It is set up this way due to the reusability of the MuiTable element.
export default function ListJobs(props) {
const url = 'http://localhost:8000/api/Jobs/'
const [data, loading] = DataLoader(url);
const handleEdit = (e,rowData) => {
<EditJob id={rowData.id} />
}
const handleDelete = (e,rowData) => {
//edit operation
<ListJobs />
DataDelete(url, rowData.id)
}
const createButton =
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Button
component={Link} to='/Job/Create'
variant="contained"
color="primary">
Create New Job
</Button>
</div>
return (
<> {loading ? (
<Grid
container
spacing={0}
alignItems="center"
justify="center"
style={{ minHeight: '90vh' }}
>
<CircularProgress size="10vh" />
</Grid>
) : (
<MuiTable
model="Job"
data={data}
url={url}
handleEdit={handleEdit}
handleDelete={handleDelete}
createButton={createButton}
/>
)}
</>
);
}
This currently throws and error "Expected an assignment or function call and instead saw an expression" on the lines that call <EditJob...> and <ListJobs>. I know this is not the correct way to write this but, I want to change it to using react routers. I have my routers set up already but don't know how to use them in this instance. I want it to work something like this.
const handleEdit = (e,rowData) => {
<component ={Link} to='Jobs' />
}
I know this isn't correct eit,her because the react router link must be inside of a component like a<button>or <MenuItem>.
Try to return EditJob and ListJobs
const handleEdit = (e,rowData) => {
return <EditJob id={rowData.id} /> // return the function <EditJob />
}
const handleDelete = (e,rowData) => {
//edit operation
DataDelete(url, rowData.id) // Any operation before the return
return <ListJobs /> // return the function <ListJobs />
}

Jest Test of Material-UI Component to Simulate Button click event to fire a Redux Action function

Not able to test button.simulate("click") on a Material-UI based Component having Button which fires 'loadAllData()' function on its onClick prop.
The below is my hooks based component
The full code for this component is here
const GithubMostPopularList = () => {
const globalStore = useSelector(state => state.globalStore)
const dispatch = useDispatch()
const loadAllData = () => {
const city = globalStore.city_to_search
setcurrentCityShown(city)
dispatch(loadMostPopularUsers(city, page, rowsPerPage))
}
return (
<div className={classes.container}>
<div className={classes.tableAndFabContainer}>
{globalStore.loading ? (
<div className={classes.spinner}>
<LoadingSpinner />
</div>
) : (
<div className={classes.table}>
<div className={classes.inputandButtonContainer}>
<Button
onClick={loadAllData}
variant="contained"
size="large"
color="primary"
disabled={globalStore.city_to_search === ""}
>
<Typography
variant="h3"
className={classes.modalButtonLabelEnabled}
>
Load City Data
</Typography>
</Button>
</div>
<div style={{ marginTop: "20px" }}>
<EachUserListItem
currentCityShown={currentCityShown}
></EachUserListItem>
</div>
</div>
)}
</div>
</div>
)
}
export default GithubMostPopularList
And below is my test, which fails giving me `TypeError: Cannot read property 'loadAllData' of null'
it("should trigger onClick on on Button press", () => {
const wrapperComp = mount(
<Provider store={store}>
<MuiThemeProvider theme={globalTheme}>
<GithubMostPopularList />
</MuiThemeProvider>
</Provider>,
)
const spy1 = jest.spyOn(wrapperComp.instance(), "loadAllData")
const button = wrapperComp.find(Button).last()
button.simulate("click")
wrapperComp.update()
expect(spy1).toHaveBeenCalledTimes(1)
})
Will highly appreciate any guidance or help.
I assume you had to use mount to avoid the error of shallowing a material component without a provider.
but here is what I usually do. I unwrap the component then it is ok to shallow it.
import unwrap from '#material-ui/core/test-utils/unwrap';
const UnwrappedComponent: any = unwrap((GithubMostPopularList as unknown as React.ReactElement<any>));
it('should trigger onClick on on Button press', () => {
const wrapperComp = shallow(<UnwrappedComponent />);
jest.spyOn(wrapperComp.instance(), 'loadAllData');
const button = wrapperComp.find(Button);
button.simulate('click');
wrapperComp.update();
expect(wrapperComp.instance().loadAllData).toHaveBeenCalledTimes(1);
});

Categories

Resources