how to properly unmount and remount a modal (open / close ) React/js - javascript

I would like to explain my problem of the day
currently I use a modal works very well
so my problem and the next one
When I close my modal I correctly unmount the states,
logically i have an animation will have to be carried out during the closing, and the nothing, I have the impression that I disassemble my component too quickly
import React, { useState } from "react";
function Lead() {
const [isModalOpen, setModalOpen] = useState(false);
const handleDisplayModal = (value) => {
setModalOpen(value);
};
return (
<div>
{isModalOpen && (
<Modal
onClose={() => handleDisplayModal(false)}
isOpen={isModalOpen}
/>
)}
<Top
setModalOpen={setModalOpen}
/>
</div>
);
}
export default Lead;
I am open to any proposal thank you very much.

Related

How would you transfer data between components in React?

I recently learned a bit about react and I'm confused about how I would transfer data between two components.
Currently, I have 2 functions implemented as such:
I have topbar.tsx which shows the topbar information, such as showing some button to open the sidebar (which is my next function).
import Sidebar from './sidebar'
export default topbar(props) {
return (
<Drawer
open={isOpen}
onclose={anotherfunction}>
<Sidebar />
</Drawer>
)
}
I also have sidebar.tsx which contains the implementation of sidebar. (This is, if I understand react terminology correctly, the child).
import CloseButton from 'react-bootstrap/CloseButton';
export default Sidebar() {
return (
<CloseButton />
)
}
The issue I'm facing is that, since the topbar is the function that controls the opening and closing of the sidebar, however, I want a close button to appear on my sidebar and do exactly as is said. How would I be able to transfer state information between these two files such that I can open sidebar and close it with the button in sidebar.
Thank you!
You elevate your state to the parent component, and pass event handler functions through.
For instance:
// Holds state about the drawer, and passes functions to mamange that state as props.
function Topbar() {
const [isOpen, setIsOpen] = useState(false)
return (
<Drawer isOpen={isOpen}>
<Sidebar onClose={() => setIsOpen(false)} />
</Drawer>
)
}
// Drawer will show it's children if open.
function Drawer({ isOpen, children }: { isOpen: boolean, children: React.ReactNode }) {
if (!isOpen) return null
return <div>{children}</div>
}
// Sidebar will send onClose to the button's onClick
function Sidebar({onClose}: { onClose: () => void }) {
return (
<CloseButton onClick={onClose} />
)
}
// Close button doesn't care what happens on click, it just reports the click
function CloseButton({onClick}: { onClick: () => void }) {
return <div onClick={onClick} />
}
Playground
First of all, rename topbar to Topbar. Otherwise you can't render your component.
For your question, you can pass the props directly to Sidebar component too.
export default Topbar(props) {
return (
<Drawer
open={isOpen}
onclose={anotherfunction}>
<Sidebar open={isOpen}
onclose={anotherfunction}/>
</Drawer>
)
}

What's the gain of hiding a Modal instead of unmounting it?

The common example of modal usually goes like this:
import * as React from 'react';
import { Modal, Portal, Text, Button, Provider } from 'react-native-paper';
const MyComponent = () => {
const [visible, setVisible] = React.useState(false);
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
const containerStyle = {backgroundColor: 'white', padding: 20};
return (
<Provider>
<Portal>
<Modal visible={visible} onDismiss={hideModal} contentContainerStyle={containerStyle}>
<Text>Example Modal. Click outside this area to dismiss.</Text>
</Modal>
</Portal>
<Button style={{marginTop: 30}} onPress={showModal}>
Show
</Button>
</Provider>
);
};
export default MyComponent;
My question is. Why is this the recommended usage instead of just unmounting the component when is not needed anymore? Something like this:
visible && (<Modal visible={true} onDismiss={hideModal} contentContainerStyle={containerStyle}>
<Text>Example Modal. Click outside this area to dismiss.</Text>
</Modal>)
Is there any pro or cons of doing it one way or another? Is there a particular or good reason I have to do it in the first("official") way?
Let me borrow this answer by jimfb to the same question on github repo of react:
Hiding via CSS is slightly faster, but it has the downside that it means your dom/react tree is bigger, which means your reconciliations are bigger (slower) and your app is using more memory - even when the tree is not visible. If you can't tell the difference between the two, in terms of performance, we would recommend unmounting (since then you're cleaning up your memory and keeping your tree small).

Why useEffect isn't changing the paragraph?

I'm studying React useEffect hook and trying to use it in a simple example. I want to have the paragraph showing modal as an effect that happens ONLY when the modal is open, and disappears when the modal is closed.
So I have only the View component in index.js, and that's the component:
import React from 'react';
import Modal from './Modal.js';
const View = () => {
let [showModal, setModal] = React.useState(false)
React.useEffect(() => {
document.getElementById('alerta').innerHTML = 'Showing modal'
return () => {
document.getElementById('alerta').innerHTML = ''
}
}, [showModal])
return(
<>
<button onClick={() => {setModal(!showModal)}}>
Show modal
</button>
<Modal showModal={showModal} setModal={setModal}/>
<p id="alerta" ></p>
</>
)
}
export default View;
Modal.js looks like this
import React from 'react';
const Modal = (props) => {
if(props.showModal){
return(
<div>
<h1>Showing modal</h1>
<button onClick={() => props.setModal(false)}>Close</button>
</div>
)
}else {
return null;
}
}
export default Modal;
As explained in the documentation, my effect returns a function that should run when the effect is cleaned, that's when the modal is closed. I also have specified that I want to run the effect only when something changes in my showModal state.
If I insert a console.log(showModal) inside my effect function, I'll see its value changing when the modal is shown or when it's closed, but the problem is, the paragraph is ALWAYS there.
Why is that happening?
Every time the effect hook runs, it will populate the paragraph:
React.useEffect(() => {
document.getElementById('alerta').innerHTML = 'Showing modal'
return () => {
document.getElementById('alerta').innerHTML = ''
}
}, [showModal])
Every render, if showModal changes, no matter what it changes to:
The cleanup from the prior render will run, clearing the content
The effect for the new render will run, populating the content
So it will always look populated.
The right way to do this would be to put the toggling logic into the JSX and use state instead of DOM methods:
const View = () => {
let [showModal, setModal] = React.useState(false)
return(
<React.Fragment>
<button onClick={() => {setModal(!showModal)}}>
Show modal
</button>
<div style={{ display: showModal ? 'block' : 'none' }}>modal here...</div>
<p id="alerta">{showModal ? 'Showing modal' : ''}</p>
</React.Fragment>
)
}
ReactDOM.render(<View />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

Prevent modal from closing after re-render in react

Inside a Component, I have a Modal from which the user can do an update to some data through an API and then change the state of that main Component. Because there is a change in state, everything will re-render.
I would like to keep the modal open, so that I can show a success message.
The code would be something like this.
const Main = () => {
const [state, setState()] = useState();
return (
<Component state={state}>
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
</Component>
)
}
When user clicks on modal's button, the state changes, and Component is re-rendered. Modal is rendered too, as it's inside.
I have thought of two possible solutions:
Move the modal outside of the component. This is a problem, as my actual code is not as simple as the example I posted. In my code, the modal opens on the click of a button B, which is deep inside Component. So, if I move the modal out from Component, I would have to pass the status and the action to change status (e.g. [open, setOpen]) through several components until button B (prop drilling).
Another solution: On the action onButtonClick I just do the API update, and use a new state updated = true; then, onModalClose, only if updated is true, I run setState so Component is rendered just after the modal is closed. But this solution seems a hack to me. There must be a better way.
Is there any better solution?
Something is obviously going very wrong here, the Modal should not close. As a workaround you could do something like this:
const Main = () => {
const [state, setState()] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
), [])
return (
<Component state={state}>{modal}</Component>
)
}
Your Modal is re-rendering because your function passed as onButtonClick is redefined at every render.
So you have 2 options here:
1/ Keep your Modal inside your Component and use useMemo
import { useMemo } from 'react'
const Main = () => {
const [state, setState] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => (
updateThroughApi().then(() => setState())
)}
/>
), [])
return (
<Component state={state}>
{modal}
</Component>
)
}
Or 2/ Move your Modal outside your component and use combination of memo and useCallback
import { memo, useCallback } from 'react'
const Main = () => {
const [state, setState] = useState();
const onButtonClick = useCallback(() => updateThroughApi().then(() => setState()), []);
return (
<Component state={state}>
<Modal onButtonClick={onButtonClick} />
</Component>
)
}
const Modal = memo(({onButtonClick}) => {
})
So in this case, at every render, memo will compare if all Modal props are === from previous render, which is now the case, because memoization of onButtonClick with useCallback, and so your Modal component will not re-render
https://reactjs.org/docs/hooks-reference.html#usememo

React render list only when data source changes

Basically I have a modal with a state in the parent component and I have a component that renders a list. When I open the modal, I dont want the list to re render every time because there can be hundreds of items in the list its too expensive. I only want the list to render when the dataSource prop changes.
I also want to try to avoid using useMemo if possible. Im thinking maybe move the modal to a different container, im not sure.
If someone can please help it would be much appreciated. Here is the link to sandbox: https://codesandbox.io/s/rerender-reactmemo-rz6ss?file=/src/App.js
Since you said you want to avoid React.memo, I think the best approach would be to move the <Modal /> component to another "module"
export default function App() {
return (
<>
<Another list={list} />
<List dataSource={list} />
</>
);
}
And inside <Another /> component you would have you <Modal />:
import React, { useState } from "react";
import { Modal } from "antd";
const Another = ({ list }) => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<Modal
visible={showModal}
onCancel={() => setShowModal(false)}
onOk={() => {
list.push({ name: "drink" });
setShowModal(false);
}}
/>
<button onClick={() => setShowModal(true)}>Show Modal</button>
</div>
)
}
export default Another
Now the list don't rerender when you open the Modal
You can use React.memo, for more information about it please check reactmemo
const List = React.memo(({ dataSource, loading }) => {
console.log("render list");
return (
<div>
{dataSource.map((i) => {
return <div>{i.name}</div>;
})}
</div>
);
});
sandbox here

Categories

Resources