I am having an issue with required PropTypes throwing errors. I would expect the PropType to throw an error if the Component was was being directly rendered. Here is a small sample of what I am trying to achieve.
You'll notice that the Button prop has a required PropType of handle click.
But I want the implementation of a Modal to be as simple as possible.
And since I don't have the context of Modal I can not bind the handleClick method directly to the Button so I pass the Button in as child and map over the children adding the handleClick method to the child component. This works pretty well besides throwing the error for Button because <Button> gets called and checked before it truly gets rendered.
I have tried to do this a few other ways as well using Higher Order Components which worked as well. But the implementation seemed convoluted and tedious this seems like a much simpler way to just generate a Modal when it is needed. You don't need any props you just pass in a child component and it will add the click handler.
It would be awesome to either Bypass the proptypes check until it is actually rendered in Modal Component or maybe there is a simpler way, all feed back is welcomed.
https://jsfiddle.net/kriscoulson/00xLw0up/1/
var Button = (props) =>
<button onClick={props.handleClick}>
{props.children}
</button>
Button.propTypes = {
handleClick: React.PropTypes.func.isRequired
}
class Modal extends React.Component {
openModal () {
console.log('Opening Modal.....')
}
childrenWithProps () {
return React.Children.map(this.props.children,(child) => React.cloneElement(child, {
handleClick: this.openModal
})
);
}
render () {
return (
<div>
{this.childrenWithProps()}
</div>
);
}
}
var App = () =>
<Modal>
<Button>Launch Modal</Button>
</Modal>
ReactDOM.render(
<App/>,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Related
I'm trying to test my Modal component with React Testing Library. The Modal is rendered inside of a React Portal. Now when I try to match my Modal with a snapshot the snapshot gets rendered as an empty div.
test('The component should render in body when open', () => {
const {container} = render(
<Modal>
<div>My dialog content</div>
</Modal>
);
expect(container).toMatchSnapshot();
});
The snapshot I get looks like this:
exports[`The component should render in body when open 1`] = `<div />`;
I've seen a few workarounds for instance passing {container: document.body} as a second argument to the render() function. But nothing really worked.
Also I cannot query for any elements via container. It always returns null.
const dialog = container.querySelector(".modal");
console.log(dialog); // null
First you should identify the container element of your portal, that "root" node that is added within the body to hold the portal content. Then you should manually append/add it in the DOM so that your render method knows where to render your Modal component. Then you should use the baseElement property instead of the container, since the portal element is rendered within the body and then test the firstChild of your baseElement (which is the actual portal element) against the snapshot.
This worked for me:
it('should render', () => {
document.body.innerHTML = '<div id="my-root-element-id" />';
const { baseElement } = render(<Default />);
expect(baseElement.firstChild).toMatchSnapshot();
});
I eventually made it work by snapshotting baseElement instead of container Both are returned properties by the render() function.
test('The component should render in body when open', () => {
const {baseElement} = render(
<Modal>
<div>My dialog content</div>
</Modal>
);
expect(baseElement).toMatchSnapshot();
});
Having looked at other stack overflow questions and on Google, I haven't been able to find an answer to this.
Background:
I'm currently re-writing a jquery application in React. Previously I was just adding a paste event listener to the body of the page. That seemed to allow me to capture any paste event the user would do.
Questions:
In React, with the code below in my App.js file, on initial load of the page it doesn't allow me to capture paste events. Only after clicking somewhere on the page does it then work when I paste. I realise I could just have an input and an onPaste attribute, but I need to be able to capture automatically.
In terms of passing the pasted text down the component tree, with the ability to edit the text from a lower component, am I right in thinking it's best to pass a callback as a prop down from the top-level component so the state is always updated using a function in the top-level component?
Currently this is what I have:
// App.js
import TopBar from "./components/TopBar/TopBar";
import Wrapper from "./components/Wrapper/Wrapper";
import AppContainer from "./components/AppContainer/AppContainer";
function App() {
const [inputText, setInputText] = useState(false);
return (
<div
onPaste={(e) => setInputText(e.clipboardData.getData("Text"))}
className="App"
>
<TopBar />
<Wrapper>
<h2>New Flashcard</h2>
<AppContainer inputText={inputText} setInputText={setInputText} />
</Wrapper>
</div>
);
}
export default App;
Your listener will only work when the specific div is focused, therefore it works on the second attempt. Instead, add the listener to body element or window object:
function Component() {
const [inputText, setInputText] = useState(false);
useEffect(() => {
window.addEventListener("paste", (e) => {
setInputText(e.clipboardData.getData("text"));
});
return () => {
window.removeEventListener("paste");
};
}, []);
return <div className="App">{inputText}</div>;
}
am I right in thinking it's best to pass a callback
No, it depends on the depth, please read about Context API and research on an anti-pattern called "props-drilling".
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;
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
I'm learning application development with Meteor and React and have run into a hurdle. I want users to be able to click on an element and have that change the class of another element. If I was creating a site without Meteor or React I would use a jQuery function like this:
$("#button").click(function(){
$("#target").removeClass("hidden");
});
I can't seem to figure out how to use jQuery in my React application (but copying the code into chrome web console works) so I started googling and found that it isn't recommended to use jQuery or to directly manipulate the DOM at all while using React. I don't understand much of how React's virtual DOM works at this stage.
So my question is: what is the correct way to replicate what this jQuery code does in a React application?
I recommend You to combine classnames (link) and state, so You can do it like this:
class Example extends React.Component {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true });
}
render() {
return (
<div className={classNames('foo', { hidden : this.state.clicked })}>
<button onClick={this.handleClick} >BUTTON</button>
</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById('example')
);
If state clicked is false, class hidden is not active on a specific element.