Creating a global modal using react-redux and foundation reveal - javascript

I'm creating a modal that displays information about a specific user and it's triggered by clicking on a picture. The modal should be able to fire up from different components. My approach is as follows (I use React with Redux):
create store with a "modal" property that's set to a reducer "modalReducer"
modal: modalReducer
modal is an object with the following properties: showModal=false, user=undefined, image=undefined
When a div containing picture is clicked, the action is being dispatched that sets showModal to true (with username and url image link as arguments).
export var openModal = (user, image) => {
return {
type: 'OPEN_MODAL',
user,
image
}
}
For the actual modal I use a foundation reveal modal and in its render method I check if modal.showModal == true and use jQuery to open it up.
Now, toggling the modal works as charm but I cannot use the modal.user and modal.image properties inside of the render method as it prints the following error message:
Uncaught Error: findComponentRoot(..., .0.2.0.0.1): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a when using tables, nesting tags like , , or , or using non-SVG elements in an parent. Try inspecting the child nodes of the element with React ID ``.
I'm almost certain the problem lies within the ProfileModal.jsx component. Any idea what I'm doing wrong?
export class ProfileModal extends React.Component {
componentDidMount () {
var elem = new Foundation.Reveal($('#profile-modal'));
$('#profile-modal').on('closed.zf.reveal',()=>{this.props.dispatch(actions.hideModal())});
}
render () {
var {modal} = this.props;
if (modal.showModal) {
$('#profile-modal').foundation('open');
}
return(
<div>
<div id="profile-modal" className="reveal large">
<div className="modal-container">
<div className="modal-picture" style={{backgroundImage: `url(${modal.image})`}}></div>
<p>{modal.user}</p>
...

Related

Svelte component created with JavaScript not receiving reactive updates

I dynamically create a component on button click with the following JS:
<script>
export let order;
function toggleModal(an_order){
modal_open.set(!$modal_open);
const modal = new OrderModal({
target: document.querySelector('.modal_container-' + order.id),
props: {
order: an_order
},
});
}
</script>
However whenever I update the order object it does not reactively reflect in the created component.
If I put the component directly in the html instead of making it dynamic like this:
<div class="modal_container-{order.id} fixed">
<OrderModal {order} />
</div>
<div class="max-w-xs w-full overflow-hidden rounded-lg shadow-md bg-white cursor-pointer" on:click="{() => toggleModal(order)}">
Then it works correctly and the elements are reactively updated.
Is there a way to make components created by JavaScript update reactively?
*** Updating to show how it is inserted ***
The function that does the update:
function deleteItem(){
order.order_items.splice(index, 1);
$order_list = $order_list;
}
As you can see I explicitly do an assignment to trigger the update which as specified works when the component is not created through javascript.
I don't know what's the context of your update function and where it's located/called from, but when creating a component programmatically/imperatively, triggering a rerender by assignment doesn't work. Instead you need to use the $set method on the component instance (docs: https://svelte.dev/docs#$set):
<script>
export let order;
let modal; // <- top level variable so it can be used in the update function
function toggleModal(an_order){
modal_open.set(!$modal_open);
modal = new OrderModal({
target: document.querySelector('.modal_container-' + order.id),
props: {
order: an_order
},
});
}
// ...
function deleteItem(){
order.order_items.splice(index, 1);
// imperatively update the order
modal.$set({order: $order_list});
}
// if you want updates of the `order` prop to propagate to the modal once it's set, you can do
$: modal && modal.$set({order: order});
</script>
The reason this doesn't work is because the compiler will create all the links between reactive elements during compilation. Your Modal component does not exist at that time so it is not possible to do so. This also explains why adding the Modal directly works.
One way to work around this is by using a store and pass this store into the modal.
In general in Svelte, you will not use the code as you have it. A more Svelte way to do it would be to use a bool to track if the Modal has to be shown or not:
<script>
let showModal = false
function toggleModal() {
showModal = !showModal
}
</script>
{#if showModal}
<Modal />
{/if}

Cannot queryselect a shadowRoot on button click(for toggling view)

I have a parent component ha-config-user-picker.js and child component edit-user-view.js.
Parent component:
has a mapping of users and also has the child component tag with its props.
the click event gets the shadowRoot of the child component and invokes method toggleView.
<template is="dom-repeat" items="[[users]]" as="user">
<paper-button on-click="clickEditUser">
<paper-item>
...
</paper-item>
</paper-button>
</template>
<edit-user-view hass="[[hass]]" user="[[user]]"></edit-user-view>
clickEditUser(ev) {
this.user = ev.model.user;
const el = this.shadowRoot.querySelector("edit-user-view");
el.toggleView();
}
Child Component:
<fullscreen-pop-up>
<dialog-header title="Edit User"></dialog-header>
<div class="content">
...
</div>
</fullscreen-pop-up>
toggleView = () => {
const popup = this.shadowRoot.querySelector("fullscreen-pop-up");
const dialog = popup.shadowRoot.querySelector("paper-dialog");
dialog.toggle();
}
Error:
So when I click on a mapped user. First I get error Uncaught TypeError: Cannot read property 'shadowRoot' of null. which is const popup = this.shadowRoot.querySelector("fullscreen-pop-up");. So the popup returns null.
But if i click any user again. it gets the shadowRoot and works fine.
Question: So why is it null the first time and works after that?
and how do I solve this?
Can this might be the problem?
https://github.com/Polymer/polymer/issues/5144
Let me know if you need something more to understand. :)
The toggleView method is not executed in the context of your child component, you can try to console.log(this) before using this and you will see it's not the element.
You can try something like replacing:
el.toggleView();
with
el.toggleView.call(el);
to see if this is the issue and if so refactor your code however you see fit. Please note that I did not test this at this moment, so there might be small differences.

v-img load dynamic images raise eror [Vuetify] Image load failed

In my case, I have two unrelated components. The first component has a button. The second component has <v-img> element of the Vuetify framework. The version of Vuetify which I use is 2.2.4. When the user clicks the button I pass some paraments by EventBus. I want to change the image in the second component after that click. For some reason in console I see that error: [Vuetify] Image load failed. eager property don't work. I also changed v-img to img. The result is the same. The image doesn't change. How to solve this problem for your opinion?
The first component (button click event logic):
EventBus.$emit('showLegend', {
imageName: 'http://domain/logo.png',
legendVisible: true
})
The second component:
<template>
<v-img
eager
v-if="legendVisible"
:src="legendURL">
</v-img>
</template>
<script>
import { EventBus } from '../../services/events.js'
export default {
data () {
return {
legendURL: 'http://domain/logo.png',
legendVisible: true
}
},
created () {
EventBus.$on('showLegend', data => {
if (data) {
this.legendURL = data.imageName
this.legendVisible = data.legendVisible
}
})
}
}
</script>
So you are saying that the image is bound correctly and displays, but the issue is with the change in URL after the emit via EventBus happened? If it had not worked in the first place, I'd have assumed you might need something related to require. Many times for static images that's the issue. You'd have to write
<v-img src="require('http://domain/logo.png')"></v-img>
However, I could not get this syntax to work with my own dynamic variables coming from a v-for="card in cards" myself until I used
<v-img :src="require('#/assets/' + card.imgSrc + '.jpg')"></v-img>
which I find rather dissatisfying.

Responding to a click on a Vue element programmatically

Essentially, I want my Vue instance to respond to a click on an uploaded thumbnail.
I'm using the FineUploader Vue package with the template layout per the docs (see end of the question). Upon uploading an image, a tree like this is outputted:
<Root>
<Gallery>
<Thumbnail>
</Gallery>
</Root>
Coming from a jQuery background I really have no idea about the 'correct' way to go about this given that the Thumbnail Template is defined by the package already, and so I'm not creating my own Thumbnail template. I know that I can access elements like this:
let thumb = this.$el.querySelector('.vue-fine-uploader-thumbnail');
And perhaps a listener
thumb.addEventListener('click', function() {
alert('I got clicked');
});
But dealing with the Vue instance being re-rendered etc. I'm not familiar with.
Vue Template:
<template>
<Gallery :uploader="uploader" />
</template>
<script>
import FineUploaderTraditional from 'fine-uploader-wrappers'
import Gallery from 'vue-fineuploader/gallery'
export default {
components: {
Gallery
},
data () {
const uploader = new FineUploaderTraditional({
options: {/*snip*/}
})
return {
uploader
}
}
}
</script>
In order to respond to click events you add the v-on:click (or it's short form: #click) to whatever tag you want.
If you have elements that are nested that respond to the click event you might experience that a click on a child triggers a parents click event. To prevent this you add #click.stop instead, so that it doesn't trigger the parents click.
So you would have something along the lines of:
<Gallery #click="myFunction" />

"Warning: react-modal: App element is not defined. Please use `Modal.setAppElement(el)` or set `appElement={el}`"

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

Categories

Resources