How can i get multiple recoil atoms when using components multiple? - reactjs

In some components i am using recoil atoms to manage my states. One example is my modal component. It look something like this:
export const modalState = atom({
key: "modalState",
default: false
})
export const useToggleModalState = () => {
const setModalState = useSetRecoilState(modalState)
return (state, callback) => {
setModalState(state)
if (callback) {
callback()
}
}
}
export const Modal = (props) => {
<Transition show={modalState}>
<Dialog>
<Dialog.Title>My Modal Headline</Dialog.title>
<Dialog.Description>My Modal Description</Dialog.Description>
</Dialog>
</Transition>
}
and i am using this modal like this:
const toggleModalState = useToggleModalState();
return (
<Modal />
<Button text="Close Modal" onClick={() => toggleModalState(false)} />
)
however, if I use the modal multiple times, the modal is automatically duplicated, but I still use only one state for all modals. Of course, I don't want that. If I use a component multiple times, I want the state to be created multiple times, so that I can change the state of each component individually.
I have read that there are also atomFamilys. Could I use these at this point? What should my code look like then? Can multiple atoms also be created automatically if I use a component multiple times?

Why do you want to use recoil for that? The state of the modal is tied to the modal itself, it doesn't need to access global state.
You can just use useState to determine if you want to show a modal within a component:
export const Modal = (props) => {
<Transition show={props.show}>
<Dialog>
<Dialog.Title>My Modal Headline</Dialog.title>
<Dialog.Description>My Modal Description</Dialog.Description>
</Dialog>
</Transition>
}
export const ComponentWithModal = () => {
const [showModal, setShowModal] = useState(false);
return (
<>
<Modal show={showModal}/>
<Button text="Open Modal" onClick={() => setShowModal(true)} />
<Button text="Close Modal" onClick={() => setShowModal(false)} />
</>
)
}

Related

Nested Modal Popups React or any other

Need modal inside modal if you click on edit then is one modal is opening. I need one more modal inside that opened modal
To create nested modal popups in React you can use a combination of modal components and conditional rendering.
Example
import React from 'react';
const ParentModal = () => {
const [showChildModal, setShowChildModal] = React.useState(false);
return (
<>
<button onClick={() => setShowChildModal(true)}>Show Child Modal</button>
{showChildModal && <ChildModal onClose={() => setShowChildModal(false)} />}
</>
);
};
const ChildModal = ({ onClose }) => {
const [showGrandchildModal, setShowGrandchildModal] = React.useState(false);
return (
<>
<button onClick={() => setShowGrandchildModal(true)}>Show Grandchild Modal</button>
{showGrandchildModal && <GrandchildModal onClose={() => setShowGrandchildModal(false)} />}
<button onClick={onClose}>Close</button>
</>
);
};
const GrandchildModal = ({ onClose }) => (
<>
<p>I'm the grandchild modal!</p>
<button onClick={onClose}>Close</button>
</>
);
export default ParentModal;
In the above example, we have three modal components: ParentModal, ChildModal, and GrandchildModal. The ParentModal component is the root of the nested modal popups, and it contains a button that will show the ChildModal when clicked. The ChildModal component, in turn, contains a button that will show the GrandchildModal when clicked. Each modal component also has a "Close" button that will close the modal when clicked.
We use the useState hook in each component to track the state of the modal (whether it is open or closed), and we use conditional rendering

Close modal if another one is opened

Im trying to create a react Modal component from scratch. I would like to add the functionality of closing the modal when another one is opened.
I know the logic how to solve(i think i know), but cant implement it. My approach would be using context, where i store the current modal opened(currentModal) and if another one is opened then it would check if there is a currentModal and if so it would close it.
So far i have the modal component:
export function Modal({title, isOpen, children, onClose}){
return(
createPortal(
<trds-modal class={isOpen ? 'opened': ''} onClick={onClose}>
<trds-modal_container onClick={e => e.stopPropagation()}>
<trds-modal_header>
<h2>{title}</h2>
<Icon icon="x" onClick={onClose} />
</trds-modal_header>
<trds-modal_body>
{children}
</trds-modal_body>
</trds-modal_container>
</trds-modal>, document.body)
)
}
i figured it out.
Created a context provider where i store the id of the current modal opened.
export function ModalContextProvider({children}){
const [currentModalId, setCurrentModalId] = useState(null);
return(
<modalContext.Provider value={[currentModalId, setCurrentModalId]}>
{children}
</modalContext.Provider>
)
}
then in the modal component i generate a uniqe id and set the context's currentModalId to that. And if the currentModalId changes then the modal checks if that equals to the modalId. If not, it calls the onClose function.
export function Modal({title, isOpen, children, onClose}){
const modalId = useMemo(() => generateId(), []);
const [currentModalId, setCurrentModalId] = useContext(modalContext);
useEffect(() => {
if(isOpen){
setCurrentModalId(modalId);
}
}, [isOpen, setCurrentModalId, modalId]);
useEffect(() => {
if(currentModalId !== modalId) onClose();
}, [currentModalId, modalId, onClose]);
return(
createPortal(
<trds-modal class={isOpen ? 'opened': ''} onClick={onClose}>
<trds-modal_container onClick={e => e.stopPropagation()}>
<trds-modal_header>
<h2>{title}</h2>
<Icon icon="x" onClick={onClose} />
</trds-modal_header>
<trds-modal_body>
{children}
</trds-modal_body>
</trds-modal_container>
</trds-modal>, document.body)
)
}
I hope it help you as i couldn't find an approach to this problem. (maybe its not even an existing problem :D)

Defining Components in React

I am creating a react website in which many pages have a specific button that should look the same for all. Should this button be its own component? If so, how would I specify the onClick events to be different for each button if it is a component?
Yes, it should be its own component.
Create it in a separate file so you can import them where you need to use.
The component should receive a onClick prop that you pass to the internal button tag.
See this example: https://reactjs.org/docs/components-and-props.html#composing-components
export const Button = ({ label, onClick, disabled }) => {
return (
<button
onClick={onClick}
disabled={disabled}
>
{label}
</button>
)
}
and then you can export this component inside any of the components you want, and pass in values like onClick and label to make it more dynamic
export const DemoFunction () => {
const onClickHandler = () => {}
return (
<Button label="Click" onClick={onClickHandler} />
)
}

I want to have a dialog as a separate component in React-MUI

I'm using React and MUI (v5)
I want to show a dialog in my component but I want this dialog as a separate component, like:
function MyComponent() {
const [ openDialog, setOpenDialog ] = useState(false);
const handleOpenDialog = () => {
setOpenDialog(true);
};
return (
<React.Fragment>
<Button variant="contained" size="medium" onClick={handleOpenDialog}>
Open Dialog
</Button>
<CreateCategory openDialog={openDialog} />
<Box>
...
</Box>
</React.Fragment>
);
}
and the dialog would be like:
export default function CreateCategory(props) {
const [openDialog, setOpenDialog] = useState(props.openDialog);
const [newCategoryName, setNewCategoryName] = useState("");
const handleDialogClose = () => {
setOpenDialog(false);
};
const handleAddCategory = (categoryName) => {
...
};
const handleCategoryNameChange = (e) => {
setNewCategoryName(e.target.value);
};
return (
<React.Fragment>
<Dialog
fullWidth
maxWidth={"sm"}
open={openDialog}
onClose={handleDialogClose}
>
<DialogTitle>Create Video Category</DialogTitle>
<DialogContent>
<TextField
...
/>
</DialogContent>
<DialogActions>
<Button ...>
Add Category
</Button>
<Button variant="outlined" onClick={handleDialogClose}>
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
But it is not working, I want to reuse the dialog in another component
I have it in code sandbox https://codesandbox.io/s/quizzical-merkle-ivquu?file=/src/App.js
Rafael
Copying props into state for no reason is an anti-pattern, and it's causing your issue.
You have two states called openDialog (one in the parent and one in the child), and are expecting them to operate as if they're the same.
To correct this, use props.openDialog directly in your component, and pass down the setter function as a prop as well. Then remove the local state version since it won't be used anymore.
// Pass the setter as well
<CreateCategory openDialog={openDialog} setOpenDialog={setOpenDialog} />
const handleDialogClose = () => {
props.setOpenDialog(false); // Use the prop.
};
<Dialog
fullWidth
maxWidth={"sm"}
open={props.openDialog} // Use value directly here
onClose={handleDialogClose}
>
In case this is also a source of confusion in the following snippet (which you should remove anyway):
const [openDialog, setOpenDialog] = useState(props.openDialog);
props.openDialog is only used once on the first render to initialize the state. Anytime in the future that the prop changes, the state will not change to match it. It is an initial value.

Call a material ui dialog

How to call a Material UI Dialog during onClick on delete icon ie onClick={deletePlayer(id)} ?
I have added the Dialog.js under modal/Dialog and imported to Home component.
I have added a demo here
Short answer: Forked CodeSandbox with working dialog
Long answer:
First of all, you need to move the display/dismiss logic out of the AlertDialog component and into the component that actually triggers the display of the modal (in your case, the Home component). This means that you'll receive the open state and onClose handler as props (along with the playerId which will hold the ID of the player being targeted for deletion). So the signature of your dialog component becomes:
export default function AlertDialog({ open, onClose, playerId }) {
return (
<Dialog open={open} onClose={onClose} ...> ... </Dialog>
);
}
In Home, we add the logic to track and set the state of both the dialog open/closed status, and the ID of the player targeted for deletion. We do this through useState:
const [deleteDialog, setDeleteDialog] = useState(false);
const [playerId, setPlayerId] = useState("");
While you could have as many AlertDialog components as you have players by adding <AlertDialog /> inside your player map loop, it is redundant as you'll only ever have one modal active (by definition). So all you have to do is place a single instance of <AlertDialog /> in your Home component. A good convention is to place it before the closing encompassing tag:
return (
<div className="App">
.
.
.
<AlertDialog
open={deleteDialog}
onClose={() => setDeleteDialog(false)}
playerId={playerId}
/>
</div>
);
Finally, we deal with the handler responsible for displaying the modal, in your case deletePlayer. We have two things to do there: set the player ID targeted for deletion through the playerId state variable, and display the modal through the deleteDialog state variable:
const deletePlayer = id => e => {
setPlayerId(id);
setDeleteDialog(true);
};
Create a state in the Home component to handle the Dialog visibility, set the state on click and render the AlertDialog conditionally:
const [openDialog, setOpenDialog] = useState(false);
...
const deletePlayer = id => e => {
setOpenDialog(true);
};
...
return(
...
{openDialog && (
<AlertDialog isOpen={openDialog} setIsOpen={setOpenDialog} />
)}
Then in the AlertDialog component:
export default function AlertDialog(props) {
const { isOpen, setIsOpen } = props;
const handleClose = () => {
setIsOpen(false);
};
return (
<div>
<Dialog
open={isOpen}
onClose={handleClose}
...
Working Example:

Resources