Manipulate HTML5 dialog in React - javascript

I'm trying to create a simple configuration dialog with close icon on top-right, but can't think of how to handle this in React. In other frameworks, I can simply use selector, and then use .showModal()/close() to open/close a dialog. However, I think we're not allowed, or not recommended to directly manipulate DOM in React, so I wonder what's the proper way in this case.
My project outline
App.js
class App extends Component {
...
...
return(
<div>
<ConfigPage />
<ConfigButton />
<MainContents />
</div>
)
}
I want to open a dialog, which is <ConfigPage />, by pressing the <ConfigButton /> I set, and close it by pressing the icon on the dialog.
config-page.js
class ConfigPage extends Component {
...
...
return(
<dialog>
<header>
<div>
<i onClick={someCallback}></i>
</div>
</header>
<section></section>
</dialog>
)
}

the HTML5 dialog also has an open attribute, correct? Instead of calling show/hide you could manipulate this attribute -
class ConfigPage extends Component {
...
...
return(
<dialog open={this.state.showDialog ? 'open' : false}>
<header>
<div>
<i onClick={someCallback}></i>
</div>
</header>
<section></section>
</dialog>
)
}
And when you want to show/hide call this.setState({showDialog: true}) (or false)
Here's a js fiddle with a proof-of-concept: https://jsfiddle.net/n5u2wwjg/193969/

Welcome to SO. You can hide a react component by return null from the render function. You can define a flag in the state that determines weather or not your component is visible. Here is a simple example.
class Modal extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: true;
};
this.onCloseClick = this.onCloseClick.bind(this);
}
onCloseClick(e) {
e.preventDefault();
this.setState({
isOpen: false,
});
}
render(){
if (!this.state.isOpen) return null;
return (
<div>
<button onClick={this.onCloseClick}>
Close
</button>
<h1>What up, this a modal</h1>
<div>
);
}
}

Related

React Component changing props on internal event

I am creating a react component where I need to show hide a dom element depending on a props. Below you will find a basic idea of the problem. The code is simplified off course.
<UploadManager
show={show}
>
{content}
</UploadManager>
The Component
export default class UploadManager extends React.Component{
openDrawer(){
// change props.open ???
}
render(){
<Drawer
visible={this.props.show}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
Explanation
show props of the component UploadManager open/close a Drawer component
The parent component can pass the show props and show/hide the Drawer. I got this part working correctly.
Need help with: The component have a floating button. When Clicked it should open (show) the Drawer. Meaning it should set this.props.show to true.
I tried to maintain a internal state of the component which manages the show/hide property of the drawer but it becomes too buggy because of the props + state involvement.
Your answer is fine except you don't need the openDrawer in the UploadManager component, actually it could be a dummy functional component, here is a possible solution :
const UploadManager = ({show, onClick}) => (
<Drawer visible={show}>
<p>Content</p>
</Drawer>
<Button onClick={onClick} />
);
Then the parent component would be
onClickShowButton = () => {
this.setState({show: true});
}
render(){
return (
<UploadManager
show={this.state.show}
onClick={this.onClickShowButton}
>
{content}
</UploadManager>
);
}
Need to pass callback from the parent to change the prop. Below is the solution to this problem.
this.onClickShowButton() is a method of the parent.
Parent Component
onClickShowButton = () => {
this.setState({show: true});
}
render(){
return <UploadManager
show={this.state.show}
onClickShowButton={this.onClickShowButton}
>
{content}
</UploadManager>
}
On the component
Checks if the callback is a function, then executes it. Does the job perfectly!
export default class UploadManager extends React.Component{
openDrawer = () => {
if (typeof(this.props.onClickShowButton) === 'function') {
this.props['onClickShowButton']();
}
}
render(){
<Drawer
visible={this.props.show}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
If so it executes it. I don't know how correct this method is. If anyone have a better answer, I am open to it.
if the parent component just sets the initial state of your upload manager to shows or hides the drawer when it first renders then you can do this:
<UploadManager
show={show}
>
{content}
</UploadManager>
export default class UploadManager extends React.Component{
constructor(props){
super(props);
this.state = {
open: props.show
}
}
openDrawer(){
this.setState({open: true});
}
render(){
<Drawer
visible={this.state.open}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
in this way the initial state of your component would be the show property that its parent passes and then when the button gets clicked, this state gets updated and the drawer is being shown based on the component state. But if you want to control the show property that the parent is passing and you want to control your drawer with that, you should do this:
<UploadManager
show={show}
onUpdateShowState={()=>{this.setState({show: true})}}
>
{content}
</UploadManager>
export default class UploadManager extends React.Component{
openDrawer(){
this.props.onUpdateShowState()
}
render(){
<Drawer
visible={this.props.show}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
in the above code you are controling your drawer with the show property that gets passed to your component and you are updating it by calling another property that updates the parent state with this: this.setState({show: true}) -> so here this referes to your parent component where show has been defined and should be written inside the parent component of the uploadManager
Do something like this:
The Component:
export default class UploadManager extends React.Component {
constructor(props){
super(props);
this.state = {
openDrawer : props.show
}
}
componentDidUpdate(){
this.setState({
openDrawer: this.props.show
})
}
openDrawer = () =>{
this.setState({
openDrawer : !this.state.openDrawer
})
}
render() {
<div>
<Drawer
visible={this.state.openDrawer}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
</div>
}
}

How to use MDCRipple.attachTo on multiple buttons in React Component

I have a simple React component that renders multiple buttons from an array in my props. I'm applying the ripple on DidMount, however, it's only attaching on the first button, the rest are being ignored. It looks like the attachTo only takes the first element. Is there another way to attach to all the buttons on didmount?
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
links
};
}
componentDidMount() {
MDCRipple.attachTo(document.querySelector('.mdc-button'));
}
render() {
return (
<section>
{this.state.links.map((link, i) => {
return (
<StyledLink key={i} to={link.url}>
<StyledButton className="mdc-button">
<StyledIcon className="material-icons">{link.icon}</StyledIcon>
<StyledTypography className="mdc-typography--caption">
{link.title}
</StyledTypography>
</StyledButton>
</StyledLink>
);
})}
</section>
);
}
}
Final markup
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj mdc-ripple-upgraded" style="--mdc-ripple-fg-size:57.599999999999994px; --mdc-ripple-fg-scale:2.1766951530355496; --mdc-ripple-fg-translate-start:-7.799999999999997px, 19.200000000000003px; --mdc-ripple-fg-translate-end:3.200000000000003px, 19.200000000000003px;">
...content
</button>
</a>
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj">
...content
</button>
</a>
Updated
I was able to find a way to use the attachTo with each button, but it still seems like there's a better way.
I changed by componentDidMount() to:
componentDidMount() {
this.state.links.forEach((link) => {
MDCRipple.attachTo(document.getElementById(`button-navbar-${link.id}`));
});
}
and then changed my render to
<StyledButton id={`button-navbar-${link.id}`} className="mdc-button">
Is there a way to do this without having to iterate through the array?
The react way to do this is to write component that injects the necessary logic.
class RippleButton extends Component {
const handleRef = elem => MDCRipple.attachTo(elem);
render() {
return (
<StyledButton {...this.props} ref={this.handleRef} />
);
}
}
Then render that component instead of your original StyledButton component and it will call the MDCRipple.attachTo() itself with its ref.
Depending on how the StyledButton is implemented you may need to use another prop to get the ref to the underlying DOM element. You did not provide enough of your code to exactly know this.

Show and hide a component based on a variable

I have created a loading icon component, which simply displays a spinner while loading something. I load it into my Sign In component, and wish to display the icon once the user clicks the Login button (And the API call is busy).
So:
import Loading from '../common/loading';
I then set an isLoading variable, defaulted to false:
this.isLoading = false;
Then, within my render method, I wish to determin if I need to show the spinner or not.
render() {
var LoadingSpinner = this.state.canLogin ? Loading : '<div></div>';
This fails.
And then my button is where I show the spinner. I'm hoping to hide the 'Sign In' text, and replace it with the spinner, but ... first thing is to handle the spinner, based on the isLoading variable.
<button
className="btn btn-lg btn-primary btn-block"
type="button"
onClick={this.handleSignin}
disabled={!this.state.canLogin}>
<span>Sign in</span> <LoadingSpinner />
</button>
</div>
Can/should this be done this way, OR... should I maybe pass a prop to my Loading component, called 'Visible' or something, and I set that?
put isLoading to constructor with default false
and then inside the render method, just add a condition
{ this.state.canLogin ? <LoadingSpinner /> : null }
Here is what you could do, using a state variable.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
}
}
onClick = () => {
this.setState({
loading: true
})
}
render() {
return (
<div>
{this.state.loading && <div>Loading</div>}
<button onClick={this.onClick}>Click to Load</button>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root'>
</div>

How to get a child component to control the parent in react?

I'm building a Modal component and want to make sure that it would easily fit a variety of different possible modal designs.
In my case it would have optional header, content and footer. I don't have an issue with this so far, the code looks like:
class ModalHeader extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div className="modal-header">
{children}
</div>
);
}
}
class ModalFooter extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div className="modal-footer">
{children}
</div>
);
}
}
class ModalContent extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div className="modal-content">
{children}
</div>
);
}
}
class Modal extends React.Component {
render () {
var header, footer, content = null;
React.Children.map(this.props.children, function(child){
if (child.type === ModalHeader) {
header = child;
}
if (child.type === ModalFooter) {
footer = child;
}
if (child.type === ModalContent) {
content = child;
}
});
return (
<div>
{header}
{content}
{footer}
</div>
);
}
}
Now comes the issue of closing the modal. I want it to be possible to close the modal from clicking an element that would be in any of the sub components, no matter, to the left or the right or anything and potentially nested deeply in the markup specific to that component.
Having a component that would wrap a piece of markup be it an X, a close icon or a button, making sure that when that element is clicked, the whole modal is closed.
class ModalCloser extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div onClick={this.close} className="modal-closer">
{children}
</div>
);
}
close () {
// no idea what goes here!!
}
}
There doesn't seem to be in React any easy way for a child component to communicate with the parent.
I would be fine with passing a prop to that closer element that would be the callback function to close the main modal, but at the place where I would be defining the modal, I don't see any way that it would be available:
<Modal>
<ModalHeader>
<div>
<header>
<h1>Title!!</h1>
<div class="float-right">
<ModalCloser closeHandler={???}>
X
</ModalCloser>
</div>
</header>
</div>
</ModalHeader>
<ModalContent>
...
</ModalContent>
</Modal>
Alternatively, I see that I could recursively traverse all the descendants and maybe do something to all descendants of type ModalCloser but I also don't really see a way to do that available.
What would be a good solution allowing to pass such a sub-component as a child or descendant to keep the layout flexibility while giving it the possibility to close the modal in such a case?
Pass a callback from the parent to the child and on close modal, call the callback in the child.
Here is a simpler version of your code, where modalClose is the callback that's being passed to the parent from the child. You can also test it in jsfiddle.
class ModalCloser extends React.Component {
render () {
return (
<div onClick={this.props.close}>
{this.props.children}
</div>
);
}
}
class Main extends React.Component {
modalClose() {
alert('closing')
}
render() {
return (<ModalCloser close={this.modalClose}>X</ModalCloser>);
}
}
ReactDOM.render(
<Main />,
document.getElementById('container')
);
In your jsfiddle you have defined the callback in the render method, therefore it was executed on every rerender of the component. I would strongly recommend you to read Thinking in React multiple times - it will answer all your questions.
Pseudocode
<Parent>
modalClose () {
console.log('modal closed...')
}
<Child modalClose={modalClose} />
<Parent/>
Now in your < Child /> whenever modal closes:
closeHandler = { this.props.modalClose }

Toggle Class in React

I'm using react for a project where I have a menu button.
<a ref="btn" href="#" className="btn-menu show-on-small"><i></i></a>
And a Sidenav component like:
<Sidenav ref="menu" />
And I wrote the following code to toggle the menu:
class Header extends React.Component {
constructor(props){
super(props);
this.toggleSidenav = this.toggleSidenav.bind(this);
}
render() {
return (
<div className="header">
<i className="border hide-on-small-and-down"></i>
<div className="container">
<a ref="btn" href="#" className="btn-menu show-on-small"><i></i></a>
<Menu className="menu hide-on-small-and-down"/>
<Sidenav />
</div>
</div>
)
}
toggleSidenav() {
this.refs.btn.classList.toggle('btn-menu-open');
}
componentDidMount() {
this.refs.btn.addEventListener('click', this.toggleSidenav);
}
componentWillUnmount() {
this.refs.btn.removeEventListener('click', this.toggleSidenav);
}
}
The thing is that this.refs.sidenav is not a DOM element and I cant add a class on him.
Can someone explain me how to toggle class on the Sidenav component like I do on my button?
You have to use the component's State to update component parameters such as Class Name if you want React to render your DOM correctly and efficiently.
UPDATE: I updated the example to toggle the Sidemenu on a button click. This is not necessary, but you can see how it would work. You might need to use "this.state" vs. "this.props" as I have shown. I'm used to working with Redux components.
constructor(props){
super(props);
}
getInitialState(){
return {"showHideSidenav":"hidden"};
}
render() {
return (
<div className="header">
<i className="border hide-on-small-and-down"></i>
<div className="container">
<a ref="btn" onClick={this.toggleSidenav.bind(this)} href="#" className="btn-menu show-on-small"><i></i></a>
<Menu className="menu hide-on-small-and-down"/>
<Sidenav className={this.props.showHideSidenav}/>
</div>
</div>
)
}
toggleSidenav() {
var css = (this.props.showHideSidenav === "hidden") ? "show" : "hidden";
this.setState({"showHideSidenav":css});
}
Now, when you toggle the state, the component will update and change the class name of the sidenav component. You can use CSS to show/hide the sidenav using the class names.
.hidden {
display:none;
}
.show{
display:block;
}
refs is not a DOM element. In order to find a DOM element, you need to use findDOMNode menthod first.
Do, this
var node = ReactDOM.findDOMNode(this.refs.btn);
node.classList.toggle('btn-menu-open');
alternatively, you can use like this (almost actual code)
this.state.styleCondition = false;
<a ref="btn" href="#" className={styleCondition ? "btn-menu show-on-small" : ""}><i></i></a>
you can then change styleCondition based on your state change conditions.
Toggle function in react
At first you should create constructor
like this
constructor(props) {
super(props);
this.state = {
close: true,
};
}
Then create a function like this
yourFunction = () => {
this.setState({
close: !this.state.close,
});
};
then use this like
render() {
const {close} = this.state;
return (
<Fragment>
<div onClick={() => this.yourFunction()}></div>
<div className={close ? "isYourDefaultClass" : "isYourOnChangeClass"}></div>
</Fragment>
)
}
}
Please give better solutions
Ori Drori's comment is correct, you aren't doing this the "React Way". In React, you should ideally not be changing classes and event handlers using the DOM. Do it in the render() method of your React components; in this case that would be the sideNav and your Header. A rough example of how this would be done in your code is as follows.
HEADER
class Header extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<div className="header">
<i className="border hide-on-small-and-down"></i>
<div className="container">
<a ref="btn" href="#" className="btn-menu show-on-small"
onClick=this.showNav><i></i></a>
<Menu className="menu hide-on-small-and-down"/>
<Sidenav ref="sideNav"/>
</div>
</div>
)
}
showNav() {
this.refs.sideNav.show();
}
}
SIDENAV
class SideNav extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
}
}
render() {
if (this.state.open) {
return (
<div className = "sideNav">
This is a sidenav
</div>
)
} else {
return null;
}
}
show() {
this.setState({
open: true
})
}
}
You can see here that we are not toggling classes but using the state of the components to render the SideNav. This way, or similar is the whole premise of using react. If you are using bootstrap, there is a library which integrates bootstrap elements with the react way of doing things, allowing you to use the same elements but set state on them instead of directly manipulating the DOM. It can be found here - https://react-bootstrap.github.io/
Hope this helps, and enjoy using React!
For anybody reading this in 2019, after React 16.8 was released, take a look at the React Hooks. It really simplifies handling states in components. The docs are very well written with an example of exactly what you need.

Categories

Resources