I'm trying to call a Modal from non-related component (without using any parent-child relationship).
In order to do that I'm trying to use React Redux (as the only way I've seen that can make a connection between two unrelated components). An example on CodeSandbox shows the bare minimum of what I'm trying to do.
My issue is that I don't want to include <Modal> inside the <Button> render function. I want to be able to simply flip the flag in Button.js and <Modal> would appear. This is, from what I understand, is suppose to be one of the advantages of Redux.
It may be look unimportant, but besides the fact that I understand that this is something that can be done and so I want to know how, it will be useful for me in a different piece of code in which if I include <Modal> in the component's render function it'll render the Modal multiple times (I render that component in a list).
Edit:
Just to be clear (as per the example on CodeSandbox), I'm using React classes and not functional components; so no hooks like useDispatch but rather functions like mapDispatchToProps are the way I want to go here.
I will Recommend using React Portal, this will inject it inside the given node, I found it to be the best solution for creating modals. I used same in dailylivedeals.com as a POC
import ReactDOM from "react-dom";
render() {
return ReactDOM.createPortal(
this.props.children,
Document.body
);
}
This is the simplest and cleanest using React's own feature.
Advantage:
Cleaner and simpler
Each modal instance can have its own modal
Multiple modals can be opened ( even from inside a modal)
Modal target can be dynamic (like modal inside modal)
Multiple modal can be controlled using code easily.
Update :
Eloborate code for modal
import React, {useEffect, useState} from "react";
import ReactDOM from "react-dom";
import {Link} from 'react-router-dom';
import "./modal.scss";
let Modal = ({visible, id, hideModal, children, ...props}) => {
let [show, setShow] = useState(false);
useEffect(() => {
setShow(visible);
console.log(visible);
}, [visible]);
let toggleVisibility = () => {
//hideModal();
setShow(!show);
}
useEffect(() => {
if (!show) {
hideModal();
}
}, [show]);
return <div className="modal-scratchpad">
{show ?
ReactDOM.createPortal(
<div id={`${id}-modal-wrapper`} className="sample-modal-wrapper">
<div id={`${id}-modal-backdrop`} className="sample-modal-backdrop">
</div>
<div id={`${id}-modal-container`} className="sample-modal-container">
<div id={`${id}-modal`} className="sample-modal">
{children}
<div onClick={toggleVisibility} className="sample-modal-cross-button">{'\u2716'}</div>
</div>
<style type="text/css">
{"body {" +
"overflow:hidden" +
"}"}
</style>
</div>
</div>
, document.body)
: <></>
}
</div>
};
export default Modal;
I am building a website using Reactjs and Gatsby.
My problem is that I have window.scrollTo(0, 240) in componentDidMount(), if I refresh the site it works well. But when I use the Gatsby Link component, the website does not go to the set position.
I have also put a console.log which works fine to see if the function runs.
Furthermore I have run the window.scrollTo(0,240) directly in the browser console which also works fine.
import React, { Component} from 'react'
import HeroHeader from "../components/HeroHeader"
import NavBar from "../components/NavBar"
import TopFooter from "../components/TopFooter"
import BottomFooter from "../components/BottomFooter"
import MainContent from "../components/MainContent"
class WebPage extends Component {
componentDidMount() {
window.scrollTo(0, 240)
console.log("Webpage Mounted")
}
render() {
// window.scrollTo(0, 240)
return(
<>
<HeroHeader />
<NavBar />
<MainContent>
{this.props.children}
</MainContent>
<TopFooter />
<BottomFooter />
</>
)
}
}
export default WebPage
Expecting to have each webpage scroll upon running the componentDidMount().
componentDidMount executes only works on initial render, you should use componentDidUpdate which will execute on every re-render,
componentDidUpdate(){
window.scrollTo(0, 240)
}
Found out it is possible to disable the window.scroll(0, 0) apparently built into Gatsby, using this guide: https://www.gatsbyjs.org/packages/gatsby-plugin-transitions/?=gatsby-plugin-transitions
Install the transition plugin:
npm install --save yarn add gatsby-plugin-transitions gatsby-plugin-layout react-spring react react-dom
(Not actually sure how many of them is actually needed to simply disable the auto scroll).
With this code in gatsby-browser.js:
exports.shouldUpdateScroll = () => {
return false;
};
Hello I have a index in a UI module within my components exporting all components in that folder like so:
~/components/ui/index.js:
import Button from './Button';
import Footer from './Footer';
export { Button, Footer }
And I use it in some page like so:
import { Button } from '~/components/ui';
export default {
components: {
Button,
Footer: () => import('~/components/ui/Footer')
}
}
Now ideally, the Footer component should be split in a different chunk, and only be loaded if needed. This works if I remove it from the exports of ~/components/ui/index.js, like so:
~/components/ui/index.js:
import Button from './Button';
//import Footer from './Footer';
export { Button, /*Footer*/ }
Now this works. But I would like to keep it in the index. And use webpack treeshaking to eliminate the Footer from the default bundle.
How can I achieve this?
How do I fix this warning in console of a React app using the react-modal package:
Warning: react-modal: App element is not defined. Please use Modal.setAppElement(el) or set appElement={el}
I have not been successful at figuring out what el is supposed to be.
Context:
in my App.js root component file:
...
import Modal from 'react-modal';
...
class App extends Component {
...
render(){
...
<Modal
className="modal"
overlayClassName="overlay"
isOpen={foodModalOpen}
onRequestClose={this.closeFoodModal}
contentLabel="Modal"
>
...
}
}
Where ... indicates code not shown.
Everything works fine, but when the Modal is opened, the following Warning appears in my console:
index.js:2177 Warning: react-modal: App element is not defined. Please use Modal.setAppElement(el) or set appElement={el}. This is needed so screen readers don't see main content when modal is opened. It is not recommended, but you can opt-out by setting ariaHideApp={false}.
In the react-modal docs all I can find is the following:
App Element
The app element allows you to specify the portion of your app that should be hidden (via aria-hidden) to prevent assistive technologies such as screenreaders from reading content outside of the content of your modal.
If you are doing server-side rendering, you should use this property.
It can be specified in the following ways:
DOMElement
Modal.setAppElement(appElement);
query selector - uses the first element found if you pass in a class.
Modal.setAppElement('#your-app-element');
Unfortunately, this has not helped! I cannot figure out what el is supposed to represent.
Here are some of the many property variations I have tried adding to my Modal component:
`appElement={el}`,
`appElement="root"` where `root` is the id that my App component is injected into
`appElement={'root'}`
`appElement="div"`,
`appElement={<div>}`,
`appElement={"div"}`
I've also tried calling Modal.setAppElement('root'); from inside src/index.js, where root is the root element that my App component is injected into, and index.js is where I do that.
Add ariaHideApp={false} to Modal attributes.
This should work:
<Modal isOpen={!!props.selectedOption}
onRequestClose={props.clearSelectedOption}
ariaHideApp={false}
contentLabel="Selected Option"
>
</Modal>
Some solutions are given in react-modal issue #133:
The problem lies here:
Depending on when it evaluates react-modal#1.6.5:/lib/helpers/ariaAppHider.js#L1:
document.body does not exist yet and it will resolve to undefined || null.
if Modal.setAppElement() is called with null or not called at all with the <script /> placed on <head /> (same as above).
Probably it can also happen if called with a selector that does not match any results.
Solutions:
Browser Rendering:
#yachaka snippet prevents this behavior by defining the element before placing the <Modal />:
componentWillMount() {
Modal.setAppElement('body');
}
#ungoldman answer, if you don't want to depend on `setAppElement':
Inject the bundled application JS into <body> instead of <head>.
Though ideally react-modal should wait until the DOM is loaded to try attaching to document.body.
server-side:
If rendering on server-side, you must provide a document.body, before requiring the modal script (perhaps it should be preferable to use setAppElement() in this case).
Update:
react docs have been updated to include the information above, so they should now be clearer for users running into this issue.
react-modal issue #567: add information (from issue #133 linked above) to the docs.
Just include appElement={document.getElementById('app')} inside your modal like this
<Modal
className="modal"
appElement={document.getElementById('app')}
>
It will work 100% if app is your central in index.html from where react loads.
This is my TypeScript Modal component which wraps react-modal v3.8.1:
import React from 'react'
import ReactModal from 'react-modal'
interface Props {
isOpen: boolean
ariaLabel?: string
}
const Modal: React.FC<Props> = ({
children,
ariaLabel = 'Alert Modal',
isOpen,
}) => (
<ReactModal
appElement={document.getElementById('root') as HTMLElement}
ariaHideApp={process.env.NODE_ENV !== 'test'}
isOpen={isOpen}
contentLabel={ariaLabel}
testId="modal-content"
>
{children}
</ReactModal>
)
export default Modal
Usage in component with state = { isOpen: true }:
<Modal isOpen={this.state.isOpen}>
<p>
Modal Content hereā¦
</p>
<button onClick={() => { this.setState({ isOpen: false }) }}>Okay</button>
</Modal>
If getting the Warning: react-modal: App element is not defined... error when running tests (we were running Jest), you can suppress the warnings by adding the following to your test file:
import ReactModal from 'react-modal';
ReactModal.setAppElement('*'); // suppresses modal-related test warnings.
The shortest solution is to add
appElement={document.getElementById("hereIsYourRootElementId")}
It lets react-modal know where is your root element.
For reference, since it was a pain for me, if you are doing SSR, use the following code to prevent errors server-side:
if (typeof(window) !== 'undefined') {
ReactModal.setAppElement('body')
}
You could put this in componentDidMount() anywhere you use a modal or I put it in a custom modal component so it's nice and DRY.
Just put this
Modal.setAppElement('#root')
This will solve the warning. The root element coming from inside public folder index.html.
you need to add # before your root element id.
import React from 'react';
import Modal from 'react-modal';
Modal.setAppElement('#root');
const OptionModal = (props) => (
<Modal
isOpen={!!props.selectedOption}
contentLabel='this is the selected option'
>
<h3>Selected Option</h3>
{props.selectedOption && <p>{props.selectedOption}</p>}
<button onClick = {props.handleCloseOptionModal}>Close</button>
</Modal>
);
export default OptionModal;
here is the reference:
http://reactcommunity.org/react-modal/accessibility/
If you get that warning on testing with the "react-testing-library" here is a solution:
https://github.com/reactjs/react-modal/issues/576#issuecomment-524644035
using the react-testing-library (https://testing-library.com/) I get rid of that warning with:
import Modal from "react-modal";
const { container } = render(<MyComponent />);
Modal.setAppElement(container);
.... // to the testing, use Modal
or, if you want to test the modal component directly:
const { container, rerender } render(<MyModalComponent isOpen={false} />);
Modal.setAppElement(container);
// now the appElement is set we can show the modal component
rerender(<MyModalComponent isOpen={false} />);
.... // to the testing
For Nextjs, I think you can solve this by adding the below to outside your modal component, maybe on top, before the component is declared.
Modal.setAppElement('#__next')
Delete this attrib
className="modal"
and run again
So i got a meteor setup with a trello like GUI, based on blaze but with react components and i want to use this library https://www.npmjs.com/package/react-s-alert for showing some popups (feel free to suggest react alternatives).
So i did this, inside a header bar inside client/components/main/header.js:
import React from 'react';
import Alert from 'react-s-alert';
import 'react-s-alert/dist/s-alert-default.css';
import 'react-s-alert/dist/s-alert-css-effects/slide.css';
...
Template.header.events({
'click .js-create-board': Popup.open('createBoard'),
'click .js-alert'() {
Alert.info('Test message')
},
});
But when the function is called, nothing happens..
Anyone can help?
I think you haven't added its initializer component in your main component.
Place sAlert component in your main app renderer component also.
<Alert stack={{limit: 3}} />