history.push is not working after using history.block - javascript

I am trying to show a message when user try to leave current page, so I am using history.block like this:
import { useHistory } from "react-router-dom";
const ProfilerCreate = ({ pageType }) => {
const history = useHistory();
const [isDisabled, setIsDisabled] = useState(true);
const [openModalUnsave, setOpenModalUnsave] = useState(false);
useEffect(() => {
history.block(validateChange);
}, []
);
//Function to validate changes and open modal
function validateChange(txt) {
if (!isDisabled) {
toggleModalUnsave();
return false;
}
}
//Function to open or close modal
function toggleModalUnsave() {
setOpenModalUnsave(!openModalUnsave);
}
//Function to return landing page
function returnPage() {
history.push("/");
}
return (
...
<div style={{ display: "none" }}>
<Modal
id="myModal"
heading="You have unsaved changes"
description="Do you want to save or discard them?"
isOpen={openModalUnsave}
onRequestClose={(detail) => toggleModalUnsave()}
actionsRight={
<>
<Button display="text" onClick={() => returnPage()}>
Discard
</Button>
<Button
display="primary"
onClick={(evt) => saveAudienceData(evt)}
>
Save and exit
</Button>
</>
}
>
<p>Modal Children</p>
</Modal>
</div>
);
export default ProfilerCreate;
when it is detecting unsaved changes, it shows a modal with a warning and two buttons, one for save and the other for discard, when the user hit discard button it should return to home page, but history.push is not working.
I tried to find the solution or I don't know if I am using the history.block in a wrong way.
I hope that you can help me, thanks!

I think you are missing the unblock() method in validateChange(txt)

Related

Antd - Is it possible to stop the spacebar from closing a popover menu?

I'm using the popover component with a text input component inside. Any time the user hits the spacebar, the popover closes.
All I really need is to stop Antd from calling onVisibleChange when the user hits the spacebar. I've tried using event.stopPropogation() and event.preventDefault on the input, but no luck. I have a bunch of dropdowns, selects, etc inside of the popover, so creating my own popover seems like it would be pretty tough to handle the handleOutsideClick functionality.
My Popover looks like:
<Popover
content={content}
title={null}
trigger="click"
getPopupContainer={(triggerNode) => triggerNode}
onVisibleChange={onChange}
visible={showMenu}
>
TLDR: I just want to stop the popover from closing when the spacebar is hit. But I also want to retain it closing if you click outside of it.
I belive the reason why spacebar will close the popover is because the button is focused by default. You could use a ref and remove focus inputRef.current?.blur(); as below.
https://codesandbox.io/s/antd-reproduction-template-forked-g0xld
import React, { useState, useRef } from "react";
import { Button, Popover, Input } from "antd";
const HistogramInsight = (props) => {
const [isVisible, setIsVisible] = useState(false);
const inputRef = useRef(null)
const handleVisibleChange = (isVisible) => {
setIsVisible(isVisible);
if (isVisible) {
inputRef.current?.blur();
}
};
return (
<div style={{ width: 300 }}>
<h2>I want to have an input in a popover/tooltip.</h2>
<h4>Hitting the spacebar with the popover open will close it.</h4>
<Popover
content={
<Input style={{ width: 200 }} placeholder="Type Hello World" />
}
title={null}
trigger="click"
visible={isVisible}
onVisibleChange={handleVisibleChange}
getPopupContainer={(triggerNode) => triggerNode}
>
<Button ref={inputRef}>Open Popover</Button>
</Popover>
</div>
);
};
export default HistogramInsight;
I think you must have a ref that indicate whether spacebar pressed or not with useRef and in visible change handler use it to decide to not hide the popover, its not a way that I like but It's probably works for you for now. if I find another better way I will tell you here
const HistogramInsight = (props) => {
const [isVisible, setIsVisible] = useState(false);
const spacePressedRef = useRef(false);
const handleVisibleChange = (isVisible) => {
if (spacePressedRef.current) {
setIsVisible(true);
spacePressedRef.current = false;
} else {
setIsVisible(isVisible);
}
};
const handleKeyUp = (event) => {
if (event.which === 32) {
spacePressedRef.current = true;
console.log("Space pressed.");
}
};
return (
<div style={{ width: 300 }}>
<h2>I want to have an input in a popover/tooltip.</h2>
<h4>Hitting the spacebar with the popover open will close it.</h4>
<Popover
content={
<Input
onKeyUp={handleKeyUp}
style={{ width: 200 }}
placeholder="Type Hello World"
/>
}
title={null}
trigger="click"
visible={isVisible}
onVisibleChange={handleVisibleChange}
getPopupContainer={(triggerNode) => triggerNode}
>
<Button>Open Popover</Button>
</Popover>
</div>
);
};
export default HistogramInsight;
Fixed Codesandbox

how to conditionally render dialog Material-ui

I am trying to show a dialog box based on the data returned from apollo hook, where I would have to check that one of the values matches an id.
When checker===true I want the dialog to open on render and when the user clicks the Close button, the dialog should close.
const DialogComponent = () => {
const {data, loading, error} = useQuery(GET_QUERY_DATA)
const [isDialogOpen, setIsDialogOpen] = useState(false);
const checker = data && data.getData.some((item_data.id === id))
const closeDialog = () => {
setIsDialogOpen(false)
}
if(checker) {
setIsDialogOpen(true)
}
return(
<Dialog
open={isDialogOpen}
close={closeDialog}>
// dialog content here
<DialogActions>
<Button onClick={closeDialog}> Close </Button>
</DialogActions>
</Dialog>
)}
The above errors with too many re-renders.
I have tried a conditional render instead however, seems that the Dialog component never opens even when checker===true (below).
const DialogComponent = () => {
const {data, loading, error} = useQuery(GET_QUERY_DATA)
const [isDialogOpen, setIsDialogOpen] = useState(false);
const checker = data && data.getData.some((item_data.id === id))
const closeDialog = () => {
setIsDialogOpen(false)
}
if(checker) {
setIsDialogOpen(true)
}
return(
{checker && <Dialog
open={isDialogOpen}
close={closeDialog}>
// dialog content here
<DialogActions>
<Button onClick={closeDialog}> Close </Button>
</DialogActions>
</Dialog>
)}}
I have also tried replacing the open prop value with checker I.e. open={checker} however, then the Dialog box never can be closed even when clicking the Close button.
Any help appreciated.
The close button does close the dialog, it is being opened again on the next render with
if(checker) {
setIsDialogOpen(true)
}
you could do:
<Dialog
open={isDialogOpen && checker}
close={closeDialog}>
<DialogActions>
<Button onClick={closeDialog}> Close </Button>
</DialogActions>
</Dialog>
One problem I see in your code is regarding this part:
if (checker) {
setIsDialogOpen(true)
}
Every time a state is updated in a component, the component funcion is called again to re-render it with the updated state. So the snippet above is executed again and if checker is true, the state is updated again, then it keeps re-redering again and again.
Try wrapping the snippet above inside a React.useEffet() like this:
React.useEffect(() => {
setIsDialogOpen(checker)
}, [checker])

Why useEffect isn't changing the paragraph?

I'm studying React useEffect hook and trying to use it in a simple example. I want to have the paragraph showing modal as an effect that happens ONLY when the modal is open, and disappears when the modal is closed.
So I have only the View component in index.js, and that's the component:
import React from 'react';
import Modal from './Modal.js';
const View = () => {
let [showModal, setModal] = React.useState(false)
React.useEffect(() => {
document.getElementById('alerta').innerHTML = 'Showing modal'
return () => {
document.getElementById('alerta').innerHTML = ''
}
}, [showModal])
return(
<>
<button onClick={() => {setModal(!showModal)}}>
Show modal
</button>
<Modal showModal={showModal} setModal={setModal}/>
<p id="alerta" ></p>
</>
)
}
export default View;
Modal.js looks like this
import React from 'react';
const Modal = (props) => {
if(props.showModal){
return(
<div>
<h1>Showing modal</h1>
<button onClick={() => props.setModal(false)}>Close</button>
</div>
)
}else {
return null;
}
}
export default Modal;
As explained in the documentation, my effect returns a function that should run when the effect is cleaned, that's when the modal is closed. I also have specified that I want to run the effect only when something changes in my showModal state.
If I insert a console.log(showModal) inside my effect function, I'll see its value changing when the modal is shown or when it's closed, but the problem is, the paragraph is ALWAYS there.
Why is that happening?
Every time the effect hook runs, it will populate the paragraph:
React.useEffect(() => {
document.getElementById('alerta').innerHTML = 'Showing modal'
return () => {
document.getElementById('alerta').innerHTML = ''
}
}, [showModal])
Every render, if showModal changes, no matter what it changes to:
The cleanup from the prior render will run, clearing the content
The effect for the new render will run, populating the content
So it will always look populated.
The right way to do this would be to put the toggling logic into the JSX and use state instead of DOM methods:
const View = () => {
let [showModal, setModal] = React.useState(false)
return(
<React.Fragment>
<button onClick={() => {setModal(!showModal)}}>
Show modal
</button>
<div style={{ display: showModal ? 'block' : 'none' }}>modal here...</div>
<p id="alerta">{showModal ? 'Showing modal' : ''}</p>
</React.Fragment>
)
}
ReactDOM.render(<View />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

Check changes before routing in React / Next js

I am having a Next JS app where there are very simple two pages.
-> Home page
import Header from "../components/header";
const handleForm = () => {
console.log("trigger");
};
export default () => (
<>
<Header />
<h1>Home</h1>
<form onSubmit={handleForm}>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit"> Login </button>
</form>
</>
);
-> About page
import Header from "../components/header";
export default () => (
<>
<Header />
<h1>About us</h1>
</>
);
Requirement:
-> Home page has a login form
-> If user started typing in any of the fields then without submitting the form, if he tries to move to About us page then a warning needs to be displayed something similar like beforeunload_event.
I am not sure how we can handle it in react as I am new to it.. Kindly please help me to handle a alert if user trying to navigate to other url while editing the form fields..
From my understanding, you can achieve your goal by listen the event routeChangeStart as then throws exception in case of rejecting to move the target url.
I forked above codesandbox and created a simple demo based on your idea which doesn't allow to switch page in case of username having value (form is dirty).
Here is the general idea:
import router from "next/router";
export default () => {
// Assume this value holds the status of your form
const [dirty, setDirty] = React.useState();
// We need to ref to it then we can access to it properly in callback properly
const ref = React.useRef(dirty);
ref.current = dirty;
React.useEffect(() => {
// We listen to this event to determine whether to redirect or not
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, []);
const handleRouteChange = (url) => {
console.log("App is changing to: ", url, ref.current);
// In this case we don't allow to go target path like this
// we can show modal to tell user here as well
if (ref.current) {
throw Error("stop redirect since form is dirty");
}
};
return (
// ...
)
}
The link codesandbox is here https://codesandbox.io/s/react-spring-nextjs-routes-forked-sq7uj

Is there a way I can close Modal without using the default Buttons on ANTD?

So I am new to ReactJS and I'm using ANT Design and currently playing around with their Modal. I want to know if we can close the Modal without using the OK and Cancel buttons.
So I removed these buttons. And created a Button inside the config. I want to close the Modal using that Button. Any help would be great! Thanks in advance!
Here is my code.
const { Modal, Button } = antd;
const ReachableContext = React.createContext();
const UnreachableContext = React.createContext();
const handleButtonOnClick = () => {
console.log('this button was clicked');
}
const config = {
visible: false,
title: 'Use Hook!', icon: null,
okButtonProps: { style: { display: 'none' } },
// cancelButtonProps: { style: { display: 'none' } },
content: (
<div>
<ReachableContext.Consumer>
{sample => (
<Button
type='primary'
block
>
Click Me Button
// IS THERE A FUNCTION THAT I CAN CLOSE THE MODAL USING THIS BUTTON?
</Button>
)}
</ReachableContext.Consumer>
</div>
),
};
const App = () => {
const [modal, contextHolder] = Modal.useModal();
return (
<ReachableContext.Provider value={modal}>
<Button
onClick={() => {
modal.confirm(config);
}}
>
Confirm
</Button>
{contextHolder}
</ReachableContext.Provider>
);
};
ReactDOM.render(<App />, mountNode);
This is how I close/show the Modal. I don't use Ok or cancel button. If the prop showForm is true then Modal will show up otherwise not.
import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../../actions";
import { Modal, Icon } from "antd";
class FormContainerModal extends Component {
state = {};
render() {
const { showForm } = this.props;
return (
<>
<Modal
title={
<div>
Title
</div>
}
destroyOnClose={true}
footer={null}
centered
maskClosable={false}
onCancel={this.props.closeModal}
visible={showForm} //it will close the modal if showForm is false
width="950px"
>
<div>
My Content
</div>
</Modal>
</>
);
}
}
const mapStateToProps = state => {
return {
showForm: state.form.showForm
};
};
export default connect(mapStateToProps, actions)(FormContainerModal);
In your case, you can change the boolean value of showForm upon button click.
<Button
type='primary'
block
onClick={()=>this.setState({showForm: false})} //here make showForm to false to close the modal
>
Close the Modal
</Button>
If you know you only have one modal open (or don't mind also closing any additional open modals), you can call the class method
Modal.destroyAll()
from anywhere and it will do the trick.
You can trigger the destruction of the modal using a button within the modal content like so:
const modal = Modal.info();
const closeModal = () => modal.destroy();
modal.update({
title: 'Updated title',
content: (
<Button onClick={closeModal}>Destroy</Button>
),
});

Categories

Resources