React — open modal in body from nested component - reactjs

React noobie... I'm trying to figure out the "correct" way to handle modals in a React App... i.e. where the modal container should live and where the children should live and whether I should use state or props to handle the open/close. Also, how to open them relative to the document body no matter where they live.
My overall App structure:
<App>
<Sidebar />
<MainConstainer>
<ChildPage>
<ChildList>
<BtnModalTrigger>
<button onClick={open.modal.relative.to.doc.body}>Open Modal</button>
</BtnModalTrigger>
</ChildList>
<Modal />
</ChildPage>
</MainConstainer>
</App>
I have the Modal component in the child page hidden, and the trigger button in the list and then the "Close modal" button is in modal itself. I'm not sure where to put my isOpen and isClosed functions or how to pass the onClicks up and down the component tree.

Related

How to open a new react component at the end of a stepper?

When I reach the end of my stepper component, how can I open a separate component that is decoupled from the stepper?
This is part of my react functional component which controls forward navigation. When I reach the 'Done' button, I want the component to open:
<ButtonContainer>
{activeStep !== 0 && (
<PrevButton onClick={handleBack}>Back</PrevButton>
)}
<NextButton
variant="contained"
color="primary"
onClick={handleNext}
>
{stepList && activeStep === stepList.length - 1
? 'Done'
: 'Next'}
</NextButton>
</ButtonContainer>
Is this something I would do in my handleNext function or do I create a separate function for this? Thanks in advance.
To open a decoupled component, you could use a Portal:
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
What you would have to do is add another <div id="my-comp"/> somewhere next to your root div (<div id="root" />). Inside this div, the Portal will be rendered, instead of as normally as a descendant of the root div. The portal component would have to return something like this from it's render() function:
render() {
return ReactDOM.createPortal(
this.props.children,
myNode,
);
}
The first argument (this.props.children) is the component, which can be an element, string, or fragment, and the second argument (myNode) is a DOM element.
You can then import the Portal component in your react functional component and place it somewhere there. But it won't be rendered as child of that component thanks to the portal.
Check out this codepen for an example.
Regarding the done button. Either create a new function and use a ternary conditition in the onClick prop or add an if to handleNext.

How can I persist my custom preview component in React Dropzone Uploader?

My DropzoneUploader component from the react-dropzone-uploader npm package has a custom preview component that is optional for display purposes. When I navigate to another route in my app using react-router-dom, the component unmounts. When i navigate to the route with the component, the optional preview component no longer has the data. It's as if there is an on/off switch for the preview component; it turns on when an action is performed, and turned off when the component unmounts (obviously).
The Preview Component is only rendered when the submit button is pressed, or autoUpload is set to true when you drag and drop a file in the zone. The docs do not give a clear way to retain the custom preview component.
<Dropzone
inputContent={<DragAndDropText key={uuid()}/>}
getUploadParams={getUploadParams}
classNames={classNames}
maxFiles={1}
accept=".dwg,.dxf"
PreviewComponent={PreviewComponent}
autoUpload={true}
maxSizeBytes={21000000}
onChangeStatus={handleChangeStatus}
initialFiles={file}
/>
It turns out that the initialFiles arg simply needed to be an array, not the actual file:
<Dropzone
inputContent={<DragAndDropText key={uuid()}/>}
getUploadParams={getUploadParams}
classNames={classNames}
maxFiles={1}
accept=".dwg,.dxf"
PreviewComponent={PreviewComponent}
autoUpload={true}
maxSizeBytes={21000000}
onChangeStatus={handleChangeStatus}
initialFiles={file} // this is wrong
/>
initialFiles={[file]} is the correct format. using redux to store input files I can conditionally render by using initialFiles={file ? [file] : null}

What is causing my component to unmount?

I'm making an app where I have an Overlay component and a Map component rendering behind it just like this:
<div>
{showOverlay && <Overlay />}
<Map /> // this component should mount once
</div>
The overlay is shown to let the map load underneath but when I remove the Overlay (set showOverlay to false here) the Map component re-load.
At first I thought the Map component was just re-rendering, but after some digging I discovered that the component was actually re-mounting.
If I log in the componentWillMount, componentWillUnmount and render methods, the log appear in that order (which seems contradictory)
render
willUnmount
willMount
The parent does not re-mount, only the Map component do.
The Map component just render a div that reference a mapbox-gl-js map (like this https://gist.github.com/tristen/5c4b346ae38892f732504e6785d87057#file-map-js)
What can cause my component to re-mount itself like that ?
Thanks !

How to change URLs (but not entire view) when clicking on a modal with Material-ui and react-router 4?

I have an app using a material ui List with ListItems.
I want for users to be able to click on a ListItem and have it open a Modal or a Collapse to give more info about the ListItem. I want the view to stay the same (overall), albeit with a new url that links to that Modal or Collapse.
Right now, all I can get is that the user clicks on the ListItem and it opens an entirely new view.
I want to have the ListDetails or ListItem details be shown in a collapse or modal (or some ui that is on that same page) and be linkable (have a url)
Code:
// List
<List>
<Collapse>
<ListItem <Link to={`/listitem/${listitem.id}`}> />
<ListDetail />
</Collapse>
<ListItem />
<Modal><ListDetail /></Modal>
</List>
// Router
<Route path="/list/:id" render={({ match }) => <ListDetail params={match.params} />} />
This goes to an entirely new page. How to wire it up to so that I can show ListDetail within the same page?
How to set up the components/routing to change urls but just change the UI (and not render an entirely new page?
Do not put <Link to={'/listitem/${listitem.id}'}> there. Instead put an on-click handler with on-click function as this.props.router.push('/bar'). This will cause the route to change.
And when modal is false you can revert the url to previous url using the same method.

Avoid `OverlayTrigger` from unmounting overlay

How do I stop the OverlayTrigger component from react-bootstrap from unmounting its overlay component when the onHide prop is called? This is the component that shows the Popover. It is not an issue with Popover since setting overlay={<ComplexComponent />} causes the same issue where ComplexComponent is mounted every time the OverlayTrigger is triggered.
This is a simplified version of what I'm attempting:
const overlay = (<Popover id="some-random-id">
<ComplexComponent />
</Popover>;
<OverlayTrigger
trigger={['hover', 'focus']}
placement="top"
overlay={overlay}
>
<span>trigger label</span>
</OverlayTrigger>
The reason I need to just hide the overlay instead of unmounting it is that ComplexComponent makes service calls on componentWillMount and takes a considerable amount of time to do that initial fetch. I've considered using refs but haven't come up with a good enough strategy.

Resources