Best practice for managing multiple modals in React Native - reactjs

I have a screen and I need to show multiple modals in it. For example, if some request was failed then I want to show an error modal, if some request was successful then I want to show a success modal.
I currently do it as following which I think is bad practice:
...
export default function SomeSampleScreen(props) {
const [errorModalVisible, setErrorModalVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [successModalVisible, setSuccessModalVisible] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
function showError(message) {
setErrorMessage(message);
setErrorModalVisible(true);
}
function showSuccess(message) {
setSuccessMessage(message);
setSuccessModalVisible(true);
}
return (
<>
<ErrorModal
visible={errorModalVisible}
onClose={() => {
setErrorModalVisible(false);
}}>
{errorMessage}
</ErrorModal>
<SuccessModal
visible={successModalVisible}
onClose={() => {
setSuccessModalVisible(false);
}}>
{successMessage}
</SuccessModal>
<View>
...
</View>
</>
);
}

You could just condense it into one object:
export default function SomeSampleScreen(props) {
const [modalState, setModalState] = useState({state: ''});
function showError(message) {
setModalState({state: "error", message});
}
function showSuccess(message) {
setModalState({state: "success", message});
}
return (
<>
<ErrorModal
visible={modalState.state === "error"}
onClose={() => {
setModalState({state: ''});
}}>
{modalState.message}
</ErrorModal>
<SuccessModal
visible={modalState.state === "success"}
onClose={() => {
setModalState(false);
}}>
{modalState.message}
</SuccessModal>
<View>
...
</View>
</>
);
}
UDPATE
After clarifying what the actual question is, here is a good way to do it.
Create a Context:
const ModalContext = React.createContext({status: "", message: ""});
Add the context and the modals somewhere up in your tree:
constructor(props) {
super(props);
this.setModal = (modalState) => {
this.setState(state => ({...state, ...modalState}));
};
this.state = {
status: "",
message: "",
setModal: this.setModal,
};
}
return <ModalContext.Provider value={this.state.modalState}>
<RestOfApp />
<Modals/>
</ModalContext.Provider>
The Modals would be similar to what you postet:
export default function SomeSampleScreen(props) {
const modalState = useContext(ModalContext);
function showError(message) {
modalState.setModal({state: "error", message});
}
function showSuccess(message) {
modalState.setModal({state: "success", message});
}
return (
<>
<ErrorModal
visible={modalState.state === "error"}
onClose={() => {
modalState.setModal({state: ''});
}}>
{modalState.message}
</ErrorModal>
<SuccessModal
visible={modalState.state === "success"}
onClose={() => {
modalState.setModal(false);
}}>
{modalState.message}
</SuccessModal>
<View>
...
</View>
</>
);
}

You can use a conditional render to return different data on the same modal.
Like this:
export default function SomeSampleScreen(props) {
const [ModalVisible, setModalVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState(false);
const [successMessage, setSuccessMessage] = useState(false);
function showError(message) {
setErrorMessage(message);
setModalVisible(true);
}
function showSuccess(message) {
setSuccessMessage(message);
setModalVisible(true);
}
return (
<>
<Modal
visible={ModalVisible}
onClose={() => {
setSuccessModalVisible(false);
setSuccessMessage(false);
setErrorMessage(false);
}}>
{errorMessage && (
{errorMessage}
)}
{ successMessage && (
{successMessage}
)}
</Modal>
<View>
...
</View>
</>
);
}

Related

How to control two elements separately for toggling states to true and false

How to control two elements separately for toggling states to true and false.Is there a way to simplify this code? Writing different states and functions for every component seems to be the wrong approach. And what if there would be more than two components?
const [active, setActiveItem] = React.useState(false);
const [active2, setActiveItem2] = React.useState(false);
function toggle() {
setActive((active) => !active);
}
function toggle2() {
setActiveItem2((active) => !active);
}
return (
<View style={styles.row}>
<CustomButton onPress={toggle} active={active} />
<CustomButton onPress={toggle2} active={active2} />
</View>
);
}
Perhaps so:
const [actives, setActives] = React.useState({
active1: false,
active2: false,
active3: false,
active4: false,
});
const toggle = (key) => setActives((actives) => ({ ...actives, [key]: !actives[key] }));
return (
<View style={styles.row}>
<CustomButton onPress={() => toggle('active1')} active={actives.active1} />
<CustomButton onPress={() => toggle('active2')} active={actives.active2} />
<CustomButton onPress={() => toggle('active3')} active={actives.active3} />
<CustomButton onPress={() => toggle('active4')} active={actives.active4} />
</View>
);
I think I would do something like this
const [active, setActiveItem] = React.useState(false);
const [active2, setActiveItem2] = React.useState(false);
function toggle(setFunction) {
setFunction((someState) => !someState);
}
return (
<View style={styles.row}>
<CustomButton onPress={()=>toggle(setActiveItem)} active={active} />
<CustomButton onPress={()=>toggle(setActiveItem2)} active={active2} />
</View>
);
}
You can use hash map for keeping state, instead of boolean
const [state, setState] = useState({
0: false,
1: true,
2: false
});
return (
Object.keys(state).map((id) => <CustomButton
key={id}
onPress={() => {setState(prev => ({...prev, [id]: !prev[id]}))}}
active={state[id]}
/>
)
Create a useToggle custom hook and use it twice:
const { useState, useCallback } = React;
const useToggle = (initial = false) => {
const [active, setActive] = useState(initial);
const toggle = useCallback(() => {
setActive(prev => !prev);
}, []);
return [active, toggle];
}
const Demo = () => {
const [active1, toggle1] = useToggle();
const [active2, toggle2] = useToggle(true);
return (
<div>
<button onClick={toggle1}>Toggle 1 - {active1 ? 'on' : 'off'}</button>
<button onClick={toggle2}>Toggle 2 - {active2 ? 'on' : 'off'}</button>
</div>
);
}
ReactDOM
.createRoot(root)
.render(<Demo />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
🟢 SIMPLE SOLUTION | using just a single state variable,
const [active, setActive] = React.useState(true);
function toggle() {
setActive(!active);
}
return (
<View style={styles.row}>
{ active ?
<CustomButton onPress={toggle} active={active} />
: <CustomButton onPress={toggle} active={!active} />
}
</View>
);
}

Warning: Failed prop type: The prop `onSubmit` is marked as required in `ProjectWizardForm`, but its value is `undefined`

This error is shown in the browser console when Dialog (modal) is open:
Warning: Failed prop type: The prop onSubmit is marked as required
in ProjectWizardForm, but its value is undefined.
Code:
const UstHomeToolbar = ({ destroyProjectWizardForm, handleOpenAddPattern }) => {
const [open, setOpen] = useState(false);
const [openUploadForm, setOpenUploadForm] = useState(false);
const [projectWizardModalTitle, setProjectWizardModalTitle] = useState("");
const classes = useStyles();
const [selectedFile, setSelectedFile] = useState();
const [isFilePicked, setIsFilePicked] = useState(false);
const [statusText, setStatusText] = useState({
type: null,
msg: null,
});
const [userData, setUserData] = useState({});
useEffect(() => {
const userDataForPermissions = localStorage.getItem(LOGGED_IN_USER);
setUserData(JSON.parse(userDataForPermissions));
}, []);
const handleProjectDialogClickOpen = () => {
setOpen(true);
};
const handleProjectDialogClose = async () => {
setOpen(false);
};
const changeUploadHandler = (event) => {
setSelectedFile(event.target.files[0]);
setIsFilePicked(true);
};
const clearUploadHandler = (event) => {
setSelectedFile(null);
setIsFilePicked(false);
};
const handleUploadSubmission = () => {
const formData = new FormData();
formData.append("excel", selectedFile);
axios
.post("/project/excel", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((res) => {
clearUploadHandler();
setStatusText({
type: "success",
msg: "Excel file is uploaded successfully and taking you to the design....",
});
const design = res.data;
window.location.href = `/project/${design.project_id}/design/${design.id}`;
})
.catch((err) => {
console.error("Error:", err?.response?.data?.message);
clearUploadHandler();
setStatusText({
type: "error",
msg: err?.response?.data?.message,
});
});
};
const handleProjectDialogExited = () => {
destroyProjectWizardForm();
};
const handleProjectWizardModalTitle = (newModalTitle) =>
setProjectWizardModalTitle(newModalTitle);
return (
<Toolbar className={classes.toolbarHome}>
<Grid container justify="space-between">
<Grid item>
<Box className={classes.projectTitleStyle}>Projects</Box>
<Box className={classes.projectTitleDetailsStyle}>
List of ongoing and finished projects
</Box>
</Grid>
<Grid item>
<div className={classes.addProjectButtonStyle}>
<IxButton
onClick={() => setOpenUploadForm(true)}
handleClose={() => setOpenUploadForm(false)}
customClassName={TOOLBAR_BUTTON}
text="UPLOAD EXCEL"
inline={true}
disabled={!getPermission(userData, PROJECT).can_edit}
/>
<IxButton
onClick={handleProjectDialogClickOpen}
customClassName={TOOLBAR_BUTTON}
text="ADD PROJECT"
inline={true}
disabled={!getPermission(userData, PROJECT).can_edit}
/>
</div>
</Grid>
</Grid>
<IxDialog
handleClose={() => setOpenUploadForm(false)}
open={openUploadForm}
modalTitle={"Upload Design Excel"}
>
<div style={{ minHeight: "50px" }}>
Select an excel (*.xls) file:
<input
type="file"
onChange={changeUploadHandler}
accept=".xls"
style={{ marginLeft: "10px" }}
/>
</div>
<div style={{ marginBottom: "5px" }}>
{statusText.type && (
<Alert className={classes.statusText} severity={statusText.type}>
{statusText.msg}{" "}
</Alert>
)}
</div>
<div>
<Button
variant="contained"
component="label"
onClick={handleUploadSubmission}
disabled={!isFilePicked}
>
Upload File
</Button>
</div>
</IxDialog>
<IxDialog
handleClose={handleProjectDialogClose}
onExited={handleProjectDialogExited}
open={open}
modalTitle={projectWizardModalTitle}
>
<ProjectWizardForm
handleOpenAddPattern={handleOpenAddPattern}
handleClose={handleProjectDialogClose}
handleProjectWizardModalTitle={handleProjectWizardModalTitle}
/>
</IxDialog>
</Toolbar>
);
};
export default UstHomeToolbar;
ProjectWizardForm component:
const ProjectWizardForm = props => {
const [page, setPage] = useState(1);
const [touchedOnLoad, setTouchedOnLoad] = useState(false);
const history = useHistory();
const {
handleClose,
dispatch,
handleOpenAddPattern,
handleProjectWizardModalTitle
} = props;
useEffect(() => {
switch (page) {
case 1:
handleProjectWizardModalTitle('Add project');
break;
case 2:
handleProjectWizardModalTitle('Add target specs');
break;
case 3:
handleProjectWizardModalTitle('Add version details');
break;
default:
return;
}
}, [page, handleProjectWizardModalTitle]);
const saveData = values => {
const { submitButtonType } = values;
var errors = ProjectValidate(values);
if (Object.entries(errors).length > 0) {
if (errors?.number || errors?.drawn_date) {
setTouchedOnLoad(true);
setPage(1);
return;
} else if (errors.mandrel_id) {
setTouchedOnLoad(true);
setPage(3);
return;
}
}
const openAddPatternForm = newProject => {
const openAddPatternForm = true;
handleOpenAddPattern(openAddPatternForm, newProject);
};
dispatch(
addDesign(
formatProjectWizardSubmittedValue(values),
(err, newProject) => {
if (!err) {
dispatch(fetchProjects());
submitButtonType === ADD_PATTERN_NEXT_SUBMIT_BUTTON_TYPE
? openAddPatternForm(newProject)
: history.push(
`/project/${newProject.project_id}/details/${newProject.id}`
);
} else {
dispatch(setError(getFormatError(err)));
}
}
)
);
handleClose();
};
return (
<div>
{page === 1 && (
<UstProjectDetailForm
getDialogActionButtons={getAddProjectDetailsDialogActionButtons}
{...props}
touchedOnLoad={touchedOnLoad}
onSubmit={saveData}
nextPage={() => setPage(page + 1)}
/>
)}
{page === 2 && (
<UstProjectTargetSpecForm
{...props}
getDialogActionButtons={getAddTargetSpecDialogActionButtons}
onSubmit={saveData}
previousPage={() => setPage(page - 1)}
nextPage={() => setPage(page + 1)}
/>
)}
{page === 3 && (
<UstDesignDetailForm
{...props}
getDialogActionButtons={getAddDesignDetailsDialogActionButtons}
touchedOnLoad={touchedOnLoad}
onSubmit={saveData}
previousPage={() => setPage(page - 1)}
nextPage={() => setPage(page + 1)}
/>
)}
</div>
);
};
ProjectWizardForm.propTypes = {
onSubmit: PropTypes.func.isRequired
};
How can I fix this issue?

How to display Material-ui Alert based on the response of axios.post reactjs

Currently, using default alert which is alert(response.data.result). To make my site more beautiful, I want to use Material-ui Alert. https://material-ui.com/components/alert/
My issue is I have no idea on how to call it from const.
Here's my code.
function Test(){
const saveData=async() => {
await axios.post('/API', passedParams)
.then(response => {
if(response.data.success === true)
{
alert(response.data.result)
}
else
{
alert(response.data.result)
//<Alert severity='error'>{response.data.result}</Alert> tried to use this but nothing displayed
}
}).catch(error=>{
alert(error)
})
//content of modal
cosnt bodyInsert = (
<div>
...fields
<Button onClick={()=>saveData()}>Save</Button>
</div>
)
return(
<div>
<Modal
open = {modalInsert}
onClose = {openCloseModalInsert}>
{bodyInsert}
</Modal>
</div>
)
}
export default Test;
Hoping for your consideration. thank you.
function Test(){
const saveData=async() => {
const [alert, setAlert] = useState(false);
const [alertContent, setAlertContent] = useState('');
await axios.post('/API', passedParams)
.then(response => {
if(response.data.success === true)
{
setAlertContent(response.data.result);
setAlert(true);
}
else
{
setAlertContent(response.data.result);
setAlert(true);
}
}).catch(error=>{
alert(error)
})
//content of modal
cosnt bodyInsert = (
<div>
...fields
<Button onClick={()=>saveData()}>Save</Button>
</div>
)
return(
<div>
{alert ? <Alert severity='error'>{alertContent}</Alert> : <></> }
<Modal
open = {modalInsert}
onClose = {openCloseModalInsert}>
{bodyInsert}
</Modal>
</div>
)
}
I think you can not do it that way. Use a state for it.
const [showAlert, setShowAlert] = useState(null);
const saveData=async() => {
await axios.post('/API', passedParams)
.then(response => {
if(response.data.success === true)
{
alert(response.data.result)
}
else
{
alert(response.data.result)
setShowAlert(response.data.result);
}
}).catch(error=>{
alert(error)
})
// Your return
return showAlert && <Alert severity='error' onClose={() => setShowAlert(null)} > { showAlert } </Alert>
You can use Collapse component collapse doc
const alertComponent = (<Alert severity='error'>{alertContent}</Alert>);
<Collapse in={alert}>{ alertComponent }</Collapse>
We can use <Alert> and <Dialog> components to create an alert in the react application
Example:
import React, { useCallback } from 'react'
import { makeStyles } from '#material-ui/core/styles'
import Alert from '#material-ui/lab/Alert'
import IconButton from '#material-ui/core/IconButton'
import CloseIcon from '#material-ui/icons/Close'
import Dialog from '#material-ui/core/Dialog'
const useStyles = makeStyles(theme => ({
root: {
'& > * + *': {
marginTop: theme.spacing(2),
},
width: '100%',
},
}))
export default function TransitionAlerts(props) {
const classes = useStyles()
return (
<div className={classes.root}>
<Dialog open={props.open}>
<Alert
action={
<IconButton
aria-label='close'
color='inherit'
size='small'
onClick={
useCallback(() => props.closeAlert({msg: '', open: false }))
}
>
<CloseIcon fontSize='inherit' />
</IconButton>
}
>
{props.msg}
</Alert>
</Dialog>
</div>
)
}

How to pass function as props from functional parent component to child

Parent Component:
const initialValue_modalProps = [
{ show: false, response: "" }
];
const [modalProps, setModalProps] = useState(initialValue_modalProps)
const passedFunction = () => {
setModalProps(modalProps => initialValue_modalProps);
}
..
..
<div>
<Modal show={modalProps.show}
response={modalProps.response}
passedFunction={passedFunction}></Modal>
</div>
Child Component:
export default function ModalComp(props) {
const [modalOpen, setmodalOpen] = useState(true);
console.log('modalOpen', modalOpen);
if (props.show === false || modalOpen === false) {
return null;
}
return (<Modal isOpen={props.show}>
<ModalHeader>Deployment Status</ModalHeader>
<ModalBody>{props.response}</ModalBody>
<ModalFooter>
<Button onClick={() => {
setmodalOpen(modalOpen => false);
props.passedFunction();
}}>Close</Button>
</ModalFooter>
</Modal>)
}
Here I want to passedFunction function from Parent to child so that the Child component can execute it to reset the state in parent
You can take this as an reference with live example demo https://codesandbox.io/s/modal-6fvyx
function App() {
const [status, setState] = React.useState(false);
const [text, setText] = React.useState("");
const handleClick = () => {
setState(prevStatus => !prevStatus);
};
const handleChange = e => {
setText(e.target.value);
};
return (
<>
<button onClick={handleClick}>Open photo entry dialog</button>
<ChildComponent
isOpen={status}
text={text}
handleChange={handleChange}
handleClick={handleClick}
/>
</>
);
}
const ChildComponent = ({ isOpen, text, handleChange, handleClick }) => {
return (
<>
{isOpen && (
<Model
status={isOpen}
handleClick={handleClick}
text={text}
handleChange={handleChange}
/>
)}
</>
);
};
You need to remove the parentheses behind passedFunction, because otherwise you are executing the function first and passing the result to the child afterwards. Pass your function as it is via passedFunction={passedFunction}.
const ParentComponent = () => {
const initialModalProps = { ... };
const [modalProps, setModalProps] = useState(initialModalProps);
const passedFunction = () => {
setModalProps(initialModalProps);
}
return (
<div>
<Modal
show={modalProps.show}
response={modalProps.response}
passedFunction={passedFunction} />
</div>
);
};
Changed the child component to this. and its working
export default function ModalComp(props) {
//const [modalOpen, setmodalOpen] = useState(true);
if (props.show === false) {
return null;
}
return (<Modal isOpen={props.show}>
<ModalHeader>Deployment Status</ModalHeader>
<ModalBody>{props.response}</ModalBody>
<ModalFooter>
<Button onClick={props.passedFunction}>Close</Button>
</ModalFooter>
</Modal>)

Using React Hooks reference with Class and Function

I've been out of the React game for awhile. Come back and I'm trying to implement the Material UI library which has been rewritten with Hooks.
It seems to be extremely confusing + spagetti code in my eyes.
I simply want to reference a function so I can toggle the drawer, how can I do this?
// Old class
export default class DefaultContainer extends Component<ViewProps, any> {
render() {
return (
<View>
<MainAppBar
onPress={() => this.onMenuPressed()}
/>
{this.props.children}
<MainDrawer
ref={'drawer'}
/>
</View>
);
}
onMenuPressed = () => {
// TODO The bit that isn't working
(this.refs['drawer'] as Drawer).handleToggle()
}
}
Now the new material UI drawer
// New Drawer (3x more code now..)
const useStyles = makeStyles({
list: {
width: 280,
},
fullList: {
width: 'auto',
},
})
type Props = {
}
function MainDrawer(props: Props, ref: any) {
const classes = useStyles();
const [state, setState] = React.useState({
left: false,
});
const toggleDrawer = () => (
event: React.KeyboardEvent | React.MouseEvent,
) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Tab' ||
(event as React.KeyboardEvent).key === 'Shift')
) {
return;
}
setState({ ...state, left: true });
};
const inputRef = useRef();
useImperativeHandle(ref, () => {
toggleDrawer()
});
const sideList = () => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer()}
onKeyDown={toggleDrawer()}
>
<List>
<ListItem button key={'drawer_item'}>
<ListItemIcon><GroupIcon /></ListItemIcon>
<ListItemText primary={'Test Item'} />
</ListItem>
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer()}>Open Left</Button>
<Drawer open={state.left} onClose={toggleDrawer()}>
{sideList()}
</Drawer>
</div>
);
}
export default forwardRef(MainDrawer);
I'm struggling to understand why you need to invoke a function from inside MainDrawer rather than just leveraging the use of props e.g.
Container
export default function DefaultContainer(props: ViewProps) {
const [drawerOpen, setDrawerOpen] = React.useState(false);
// assuming it's a toggle?
const toggleMenu = React.useCallback(() => setDrawerOpen(open => !open));
return (
<View>
<MainAppBar onPress={toggleMenu} />
{this.props.children}
<MainDrawer open={drawerOpen} />
</View>
)
}
MainDrawer
function MainDrawer(props: Props) {
const [open, setOpen] = React.useState(props.open);
...
const toggleDrawer = React.useCallback(() => setOpen(open => !open));
return (
<div>
<Button onClick={toggleDrawer}>Open Left</Button>
// use prop to determine whether drawer is open or closed
<Drawer open={open} onClose={toggleDrawer}>
{sideList()}
</Drawer>
</div>
);
}

Resources