How to dismiss a React Modal using createPortal()? - reactjs

Trying to dismiss a Modal dialog from within the Modal. I'm using ReactDOM.createPortal().
index.html
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<div id="modal"></div>
</body>
ResponseModal.js
import React from "react";
import ReactDOM from "react-dom";
// The gray background
const backdropStyle = {
...
};
// The modal "window"
const modalStyle = {
...
};
const MODAL_ROOT = document.querySelector("#modal");
const JSX_MODAL = ({ children, setShowResponses }) => (
<div style={backdropStyle}>
<div style={modalStyle}>
Child: {children}
<br />
<input
type="button"
value="Dismiss"
onClick={() => {
setShowResponses(false);
}}
/>
</div>
</div>
);
function Modal({ showResponses, ...props }) {
console.log("floop", showResponses);
if (showResponses) {
return ReactDOM.createPortal(JSX_MODAL(props), MODAL_ROOT);
}
return null;
}
export default Modal;
And finally, the containing component:
const LargeCell = ({ SCID, extra, fen, color }) => {
const [showResponses, setShowResponses] = useState(false);
return (
<div
style={{
...
}}
onClick={() => setShowResponses(true)}
>
<SmallCell {...{ SCID, color }} />
<DiagramForCell {...{ fen }} padding="3em"></DiagramForCell>
<span className="diff-text opening-text">{extra.opening.desc}</span>
<ResponsesModal {...{ showResponses, setShowResponses }}>
FLUM!
</ResponsesModal>
</div>
);
};
When I click on the LargeCell div, I see:
However, the dismiss button doesn't work. I'm sure that setShowResponses(false) is called, but there is no re-rendering of the Modal, so it is not dismissed. If I look at the Modal component in Chrome devtools, the state of showResponses still shows true.
So the question is: what is the correct way to dismiss this Modal?

So LargeCell was a table cell component, of which there were multiple. What I did was to push the Modal to the table level, and show/hide it from there:
{showResponses ? (
<ResponsesModal {...{ setShowResponses }}>FLUM!</ResponsesModal>
) : null}
setShowResponses is called by the Dismiss button in the Modal as shown previously.
The disadvantage is that to bring up the modal, each LargeCell needs setShowResponses, also. That prop has to be pushed several levels down. That's okay for now, but I'm starting to wonder if I should use a context.

Related

Function call and pass value in the same OnClick - React

I got a OnClick which actually receives an id:
<Button onClick={() => addToCart(id)} >Buy</Button>
On the other hand, in a different JS file,I got a modal which appears with a click via useState:
const [stateModal1, changeModalState1] = useState(false);
Now, in the same component I work with this modal, I map an array which returns a Button, which now is working with the "addToCart(id)" value mentioned before, like this:
{products.map((product) => {
return <Product image={product.image}
key={product.id}
data={product}
addToCart={() =>addToCart(product.id)} />})}
The question that is driving me crazy is: how can I use the button in the mapped array to trigger that modal, and at the same time, to pass values to that modal in order to show the mapped item IN that modal?
Thanks in advance.
EDIT: this is the modal, which is another component:
const Modal = ({
children,
state,
stateModal1,
})
return (
<>
{state &&
<Overlay>
<Container>
<CloseButton onClick={() => changeState(false)}>{FaWindowClose}</CloseButton >
{children}
<Header>
<h3>Confirm buy</h3>
<h4>{name}</h4>
<h4>$ {price}</h4>
</Header>
<Button onClick={() => changeState(false)}>Confirm</Button>
</Container>
</Overlay>
}
</>)
PS: the "confirm" button which triggers the "changeState()", should also trigger the addToCart().
As mentioned by other comments above, you can pass a prop to the modal component from the parent component to achieve your demand normally.
The only thing that needs to be done is set the open/close modal state and the passing data state at the same time, or, probably use one state directly
sample of the code:
import "./styles.css";
import "antd/dist/antd.css";
import { useState } from "react";
import { Modal } from "antd";
export default function App() {
// init with undefined, if not undefined, open the modal
const [modal, setModal] = useState(undefined);
const list = [...Array(20).keys()];
// set the state to open the modal, as well as pass it to the modal itself as a prop if necessary
const handleClick = (idx) => () => {
setModal(idx);
};
return (
<div className="App">
{list.map((x, idx) => (
<div style={{ border: "1px solid black" }} onClick={handleClick(idx)}>
{x}
</div>
))}
<Modal visible={modal !== undefined}>The value you passed: {modal}</Modal>
</div>
);
}
the online demo could be found here: https://codesandbox.io/s/hardcore-shape-89y78?file=/src/App.js

React - execute javascript after bootstrap modal is completely rendered and visible

I'm working on a React front-end that uses Reactstrap, in which I'm creating my own reusable modal component. Whenever there is too much content for the modal, it becomes scrollable and to make that clear to the user, I created an indicator at the bottom of the modal. (example screenshot)
The indicator sticks at the bottom of the modal while scrolling and I make it disappear when the user reaches the end (check onscroll event and moreContent state in code below).
So far so good, but my problem is that I can't find a way to check if I initially have to show the indicator when rendering the modal. Right now the moreContent state is initially set to true, but that should depend on whether the modal is scrollable or not.
I tried:
to find an event like onScroll that fires when Modal is rendered so that I can check if event.target.scrollHeight == event.target.clientHeight
useEffect hook with a reference to the modal. This fires too soon because scrollHeight and clientHeight are still 0.
The code for my modal component:
import React, {useEffect, useState} from 'react';
import {Button, Modal, ModalBody, ModalHeader} from "reactstrap";
const MyModal = (props) => {
const [moreContent, setMoreContent] = useState(true);
const ref = React.useRef();
useEffect(() => {
console.log("scrollHeight", ref.current._element.scrollHeight);
console.log("scrollTop", ref.current._element.scrollTop);
console.log("clientHeight", ref.current._element.clientHeight);
});
const onScroll = (event) => {
if (moreContent) {
setMoreContent(event.target.scrollHeight - event.target.scrollTop !== event.target.clientHeight);
}
}
return (
<Modal isOpen={props.isOpen} toggle={props.closeHandler} centered={true} scrollable={true} className="my-modal" onScroll={onScroll} ref={ref}>
<ModalHeader tag="div" toggle={props.closeHandler}>
...
</ModalHeader>
<ModalBody>
{props.children}
{moreContent &&
<div className="modal-scroll-down">
<i className="fa fa-arrow-down mr-4"></i> MEER <i className="fa fa-arrow-down ml-4"></i>
</div>
}
</ModalBody>
</Modal>
)
};
MyModal.defaultProps = {
showCloseButton : true
}
export default MyModal;
Any tip, advice, workaround is welcome.
Thanks in advance!
The issue here is that you are attaching ref to a Modal which is not a DOM element and therefore will not have these properties scrollHeight, scrollTop, and clientHeight. Furthermore even if it was a DOM element, it is not the element with the scrollbar - it is actually ModalBody. But, to make matters worst, it looks like Reactstrap does not really expose a prop for you attach a forwarded ref to the ModalBody.
To solve this you can replace ModalBody with a div - this is where we can attach a ref to.
<Modal
isOpen={props.isOpen}
toggle={props.closeHandler}
centered={true}
scrollable={true}
className="my-modal"
onScroll={onScroll}
onOpened={onOpened}
>
<ModalHeader tag="div" toggle={props.closeHandler}>
modal header
</ModalHeader>
<div ref={ref} style={{ overflowY: "auto", padding: "16px" }}>
{props.children}
{moreContent && (
<div className="modal-scroll-down">
<i className="fa fa-arrow-down mr-4"></i> MEER{" "}
<i className="fa fa-arrow-down ml-4"></i>
</div>
)}
</div>
</Modal>
Pay attention to the onOpened prop I attached to <Modal>, this answers what you sought:
execute javascript after bootstrap modal is completely rendered and
visible
const onOpened = () => {
setMoreContent(
ref.current.scrollHeight - ref.current.scrollTop !==
ref.current.clientHeight
);
};

why destroyOnClose={true} not working in React

I am developing a React hook based functional application with TypeScript and I am using modal from ant design. I'm submitting a form through modal for a table. So, the modal will be called for more than once to fill-up different rows of the table.
The problem is, when the modal is popping up for the second, third or lateral times, it's always carrying the previous values.
To avoid that, I set in the modal EnableViewState="false" , it didn't work . I set
destroyOnClose={true}. It didn't work. In the modal documentation, it is written when destroyOnClose doesn't work then we need to use . But where to define it ? Because, when I am setting up as,
<Form onSubmit={props.inputSubmit} preserve={false} in my modal form, I'm getting an error saying Type '{ children: Element[]; onSubmit: any; preserve: boolean; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Form>......?
what do you use so that every time the modal reloads, it reloads as empty ? I don't want to assign the state in the form value fields of the input. Is there any other option such as, destroyOnClose={true} ?
Here is my modal,
<Form onSubmit={props.inputSubmit}>
<Row>
<Col span={10}>
<Form.Item>
<Text strong={true}>Article name: </Text>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item>
<Input
style={{ backgroundColor: '#e6f9ff' }}
name="articleName"
onChange={props.handleArticleModalInput}
/>
</Form.Item>
</Col>
</Row>
</Form>
Here is from where the modal is getting called,
return (
<>
<ArticleTableModal
destroyOnClose={true}
isVisible={modalVisibilty}
inputSubmit={inputSubmit}
handleCancel={handleCancel}
filledData={fetchedData}
articleNumber={articleNumber}
handleArticleModalInput={handleArticleModalInput}
/>
<Table
pagination={false}
dataSource={articleDataSource}
columns={articleColumns}
scroll={{ y: 400 }}
bordered
/>
</>
)
Any help is much appreciated.
You need to generate dynamic keys for the fields in the form on each modal launch.
Here's a sandbox to play around. If you don't make any changes to the key, the modal retains values inside it. If you change key and launch modal, the value gets cleared.
Sandbox Link
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Modal, Button, Input } from "antd";
class App extends React.Component {
state = { visible: false, theKey: "dummy" };
showModal = () => {
this.setState({
visible: true
});
};
handleOk = (e) => {
console.log(e);
this.setState({
visible: false
});
};
handleCancel = (e) => {
console.log(e);
this.setState({
visible: false
});
};
handleChange = ({ target: { value } }) => {
this.setState({ theKey: value });
};
render() {
return (
<>
<Input onChange={this.handleChange} placeholder="key for input field"/>
<br />
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<Input
key={this.state.theKey}
style={{ backgroundColor: "#e6f9ff" }}
name="articleName"
/>
</Modal>
</>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Here we'll use a custom hook that wraps a ModalDialog component from somewhere else (like a 3rd party UI library) and gives us back a tuple of a setter and a self-contained component/null. Hooks make this neater but you can still accomplish all of this with class components at the cost of a little verbosity. Since you tagged Typescript this should all be straightforward but you may have to specify that your use of useState is useState<React.ReactChild>(); to avoid type errors.
const useDialog = (ModalComponent) => {
const [modalDialogState, setModalDialogState] = useState();
return modalDialogState
? [
setModalDialogState,
// You may have to tweak this a bit depending on
// how your library works.
() => (
<ModalComponent onClose={() => setModalDialogState('')>
{modalDialogState}
</ModalComponent>
),
]
: [setModalDialogState, null];
};
const MyComponent = () => {
const [setModal, Modal] = useDialog(WhateverLibraryComponent);
useEffect(() => {
// Cleanup function, run on unMount and clears dialog contents.
return () => setModal('');
}, [setModal]);
return Modal
? (
<>
<Modal />
/* Normal render stuff */
</>
)
// You can optionally render a button here with onClick
// set to a function that calls setModal with some
// appropriate contents.
: (/* Normal render stuff */)
};

How to make a bold button for an editable div in react.js

I would like to make an editor from scratch with a contenteditable div in react.js
The div is editable, but how can I add a button to make selected text bold?
I tried "document.execCommand('bold', false, null);", but cant make it work.
import React from 'react';
const Editor = () => {
return (
<div className="editor">
<div className="toolbar">
<button onclick={(e) => {
document.execCommand('bold', false, null);
e.preventDefault();
}}>b</button>
</div>
<div className="editor-content" contenteditable>
<h1>Test</h1>
<p>Test</p>
</div>
</div >
)
}
export default Editor;
Props are case-sensitive. Refactor onclick to onClick and contenteditable to contentEditable and this implementation should work

Access Gatsby Component from a function

I am trying to access a Gatsby component (Anime) from outside of it.
Can not figure out what instance name this would have or how to name it.
Here is my code:
import React from 'react'
import PropTypes from 'prop-types'
import PreviewCompatibleImage from '../components/PreviewCompatibleImage'
import Anime from 'react-anime';
import VisibilitySensor from 'react-visibility-sensor';
function onChange (isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden')
}
const FeatureGrid = ({ gridItems }) => (
<div className="columns is-multiline">
<VisibilitySensor onChange={onChange}>
<Anime delay={(e, i) => i * 100}
scale={[.1, .9]}
autoplay={false}>
{gridItems.map(item => (
<div key={item.text} className="column is-3">
<section className="section">
<div className="has-text-centered">
<div
style={{
width: '160px',
display: 'inline-block',
}}
>
<PreviewCompatibleImage imageInfo={item} />
</div>
</div>
<p>{item.text}</p>
</section>
</div>
))}
</Anime>
</VisibilitySensor>
</div>
)
FeatureGrid.propTypes = {
gridItems: PropTypes.arrayOf(
PropTypes.shape({
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
text: PropTypes.string,
})
),
}
export default FeatureGrid
I want to get the animation to trigger from the onChange function.
How do I get the name or set the name of the Anime component so I can access it from the function?
Or is there another way I should address this?
Using a Gatsby starter netlify CMS as the base, so extending on their code, but seems that const is not the route I should take.
I want the animation to trigger when it becomes visible.
Any suggestions?
According to the docs react-visibility-sensor :
You can pass a child function, which can be convenient if you don't need to store the visibility anywhere
so maybe instead of using the onchange function you can just pass the isVisible parameter, something like:
<VisibilitySensor>
{({isVisible}) =>
<Anime delay={(e, i) => i * 100}
// the rest of your codes here ...
</Anime>
}
</VisibilitySensor>
Otherwise you can convert this function to a react component and set states, etc..

Resources