I'm using primereact version 6.2.1, While using the calendar from primereact i have enabled the option for showTime. This brings up a datePicker with an overlay blocking out the rest of the webpage. All this works fine. Except after a date and time are picked the only way to close the datePicker is to click somewhere else on the browser. Is there a way to close it manually ?
import React, { useRef } from 'react';
import { Calendar } from 'primereact/calendar';
const CustomCalendar = (props) => {
const cal = useRef(null);
const handleClose = () => {
cal.current.hideOverlay()
// if you're using touchUI option you have to hade it also ;)
cal.current.touchUIMask.style.display = "none";
};
return <Calendar
ref={cal}
{...props}
footerTemplate={(e) => <div className='footer-calendar'><button onClick={handleClose}>Done</button></div>}
/>
}
You can close overlay manually using ref, you can check this link
sandbox
or check the github link for more options
github
If you get a ref to your Calendar you can call calendarRef.current.hideOverlay();
I also opened this ticket: https://github.com/primefaces/primereact/issues/2685
Submitted this PR: https://github.com/primefaces/primereact/pull/2687
Related
I have a simple component with a draggable icon next to it (going to implement my own Resize).
The code is as follows:
type ICanvas = {
currPageId:string
}
const Canvas:FC<ICanvas> = ({currPageId}) => {
const story = useStoryStore(state=>state.story);
const handleDragStart = (e:any) => {
console.log("start")
};
const handleOnDrag = (e:any) => {
console.log("on drag");
};
return (
<Box {...Styles.CanvasContainer} pos='relative'>
<div
className="resize-button"
draggable
onDrag={handleOnDrag}
onDragStart={handleDragStart}
onDragEnd={handleOnDrag}
>
<HorizonIcon type='arrow_chevron_down'/>
</div>
</Box>
)
}
export default Canvas;
The onDragStart event fires as it should, but onDrag doesn't work. I tried adding e.stopPropagation/preventDefault everywhere in all possible permutation but nothing.
I even tried using the Chrome inspect event breakpoints and nothing.
I am using latest react version with Chakra UI provider.
Thanks!
So after some digging and a little bit of luck I found out that I had in my system a dependancy of React-dnd-HTML5backend.
With a little google research I found out that this library causes problem with native dragging events within every application it is installed in.
So the solution would be using react-dnd that I already have installed in my project.
I use using bootstrap 5 in my NextJs app my installing like this:
npm install bootstrap and imported the necessary files into my project via _app.js like this:
import 'bootstrap/dist/css/bootstrap.css'
...
useEffect(() => {
import("bootstrap/dist/js/bootstrap");
}, []);
Now the problem is that when I open the offcanvas modal and click a link from there, the offcanvas stays open after the page changes, instead of closing. How do I programmatically close the offcanvas when the pathname changes and only when the offcanvas is active. Thank you
NB: I am not using react-bootstrap
Import like this:-
useEffect(() => {
import("bootstrap/dist/js/bootstrap.bundle");
}, []);
Recently I ran into the same problem, but here is how I got things working.
First of I do not think you are importing bootstrap in to your nextjs app the right way, but what works work. However I think you should import bootstrap into your nextjs app this way.
useEffect(() => {
typeof document !== undefined
? require("bootstrap/dist/js/bootstrap")
: null;
}, []);
I don't want to make this too long, so lets dive straight into creating the solution. First you have to create a custom function to close the offCanvas when you click your link.
const topicRef = useRef(null);
const closeOffCanvas = (event) => {
event.stopPropagation();
const bootstrap = require("bootstrap/dist/js/bootstrap");
var myOffcanvas = topicRef.current;
var bsOffcanvas = bootstrap.Offcanvas.getInstance(myOffcanvas);
bsOffcanvas.hide();
};
Now create your link like so and call the function we just created
<Link href={`/article/18004/close-bootstrap-5-offcanvas-in-next-js`} passHref>
<a href="#" onClick={closeOffCanvas}>navigate to page then close offCanvas</a>
</Link>
I hope this helps you
I have modal screen (using react-bootstrap), on modal screen i have multiple overlays (popup menus) linked to items. These overlays has inputs, and when i click on input it immediately loses focus. I cant figure out whats wrong, because another one popup menu, that i have on normal screen, not modal, works fine. Tried to set autofocus, but it immediately loses too.
I wrote example, https://codesandbox.io/s/rkemy
I think it is somehow connected with popper, because bootstrap overlay uses it, dont know where to dig
The fix provided in the other response is a workaround that doesn't fix the real cause of the issue.
The issue is caused by an internal logic of the Modal component of react-overlay library that is a dependency library of react-bootstrap.
Specifically, the issue is caused by code listed below
const handleEnforceFocus = useEventCallback(() => {
if (!enforceFocus || !isMounted() || !modal.isTopModal()) {
return;
}
const currentActiveElement = activeElement();
if (
modal.dialog &&
currentActiveElement &&
!contains(modal.dialog, currentActiveElement)
) {
modal.dialog.focus();
}
});
that enforce the focus on the first modal open as soon as that modal lose the focus, like when you move the focus on the input.
In order to solve the issue, you have to pass the enforceFocus={false} to your Modal component.
The documentation of the API can be found here: https://react-bootstrap.github.io/react-overlays/api/Modal#enforceFocus
As the docs says:
Generally this should never be set to false as it makes the Modal less accessible to assistive technologies, like screen readers. but in your scenario this is a need to work properly.
Solution is to wrap Overlay in container:
import React from "react";
import { Overlay } from "react-bootstrap";
import { X } from "react-bootstrap-icons";
export const PopupMenuWrapper = (props) => {
const { target, title, show, onClose, children } = props;
const ref = React.useRef(null);
return (
<div ref={ref}>
<Overlay
container = {ref.current}
target={target.current}
show={show}
placement="bottom-start"
rootClose={true}
onHide={onClose}
>
...
</div>
...
I'm currently migrating to antd, and have a modal appear on a certain route (ie /userid/info). I'm able to achieve this if I use the antd Modal react component, but I'd like to be able to use the modal methods provided such as Modal.confirm,Modal.info and Modal.error as they offer nicer ui straight out of the box.
I'm running to multiple issues such as having the modal rendered multiple times (both initially and after pressing delete in the delete user case), and unable to make it change due to state (ie display loading bar until data arrives). This is what i've tried but it constantly renders new modals, ive tried something else but that never changed out of displaying <Loader /> even though isFetching was false. I'm not sure what else to try.
const UserInfoFC: React.FC<Props> = (props) => {
const user = props.user.id;
const [isFetching, setIsFetching] = React.useState<boolean>(true);
const [userInfo, setUserInfo] = React.useState<string>('');
const modal = Modal.info({
content: <Loader />,
title: 'User Info',
});
const displayModal = () => {
const renderInfo = (
<React.Fragment>
<p>display user.info</p>
</React.Fragment>
);
const fetchInfo = async () => {
try {
user = // some api calls
setUserInfo(user.info);
modal.update({ content: renderInfo })
} catch (error) {
// todo
}
setIsFetching(false);
};
fetchInfo();
};
displayModal();
return(<div />);
};
reference: https://ant.design/components/modal/#components-modal-demo-confirm
edit: here is a replication of one of the issues I face: https://codesandbox.io/embed/antd-reproduction-template-1jsy8
As mentioned in my comment, you can use a useEffect hook with an empty dependency array to run a function once when the component mounts. You can initiate an async call, wait for it to resolve and store the data in your state, and launch a modal with a second hook once the data arrives.
I made a sandbox here
Instead of going to /:id/info and routing to a component which would have returned an empty div but displayed a modal, I created a displayInfo component that displays a button and that controls the modal. I got rid of attempting to use routes for this.
What I have now is similar to the docs
I'm experiencing some problems with scroll positions on the back button (history popstate) when using react-router. React router v4 doesn't handle scroll management out of the box because browsers are implementing some automatic scroll behavior. This is great except when the height of the browser window changes too dramatically from one view to another. I have implemented the ScrollToTop component as described here: https://reacttraining.com/react-router/web/guides/scroll-restoration
This works great. When you click a link and go to a different component, the browser scrolls to the top (like a normal server-rendered website would). The issue only happens when you go back (via the browser back button) to a view with a much taller window height. It seems that (chrome) tries to go to the scroll position of the previous page before react has rendered the content (and browser height). This results in the scroll only going as far down as it can based on the height of the view it's coming from. Picture this scenario:
View1: Long list of movies (window height 3500px).
(movie is clicked)
View2: Details view of the selected movie (window height: 1000px).
(Browser back button is clicked)
Back to view 1, but scroll position can't go further than 1000px, because chrome is trying to set the position before react renders the long movie list.
For some reason this is only a problem in Chrome. Firefox and Safari seem to handle it fine. I wonder if anyone else have had this problem, and how you guys generally handle scroll restoration in React.
Note: all the movies are imported from a sampleMovies.js — so I'm not waiting for an API response in my example.
Note that history.scrollRestoration is just a way of disabling the browser's automatic attempts at scroll restoration, which mostly don't work for single-page apps, so that they don't interfere with whatever the app wants to do. In addition to switching to manual scroll restoration, you need some sort of library that provides integration between the browser's history API, React's rendering, and the scroll position of the window and any scrollable block elements.
After not being able to find such a scroll restoration library for React Router 4, I created one called react-scroll-manager. It supports scrolling to top on navigation to a new location (aka history push) and scroll restoration on back/forward (aka history pop). In addition to scrolling the window, it can scroll any nested element that you wrap in an ElementScroller component. It also supports delayed/asynchronous rendering by using a MutationObserver to watch the window/element content up to a user-specified time limit. This delayed rendering support applies to scroll restoration as well as scrolling to a specific element using a hash link.
npm install react-scroll-manager
import React from 'react';
import { Router } from 'react-router-dom';
import { ScrollManager, WindowScroller, ElementScroller } from 'react-scroll-manager';
import { createBrowserHistory as createHistory } from 'history';
class App extends React.Component {
constructor() {
super();
this.history = createHistory();
}
render() {
return (
<ScrollManager history={this.history}>
<Router history={this.history}>
<WindowScroller>
<ElementScroller scrollKey="nav">
<div className="nav">
...
</div>
</ElementScroller>
<div className="content">
...
</div>
</WindowScroller>
</Router>
</ScrollManager>
);
}
}
Note that an HTML5 browser (10+ for IE) and React 16 are required. HTML5 provides the history API, and the library uses the modern Context and Ref APIs from React 16.
How do you handle your scroll restoration?
Turns out browsers have implementations of the history.scrollRestoration.
Maybe you can use that? Check these links out.
https://developer.mozilla.org/en/docs/Web/API/History#Specifications
https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration
In addition, I found an npm module that might be able to handle scroll restoration in react with ease, but this library only works with react router v3 and below
https://www.npmjs.com/package/react-router-restore-scroll
https://github.com/ryanflorence/react-router-restore-scroll
I hope this can help.
my solution:save window.scrollY for every pathname with a Map (ES6)
scroll-manager.tsx
import { FC, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export function debounce(fn: (...params: any) => void, wait: number): (...params: any) => void {
let timer: any = null;
return function(...params: any){
clearTimeout(timer);
timer = setTimeout(()=>{
fn(...params)
}, wait);
}
}
export const pathMap = new Map<string, number>();
const Index: FC = () => {
const { pathname } = useLocation();
useEffect(() => {
if (pathMap.has(pathname)) {
window.scrollTo(0, pathMap.get(pathname)!)
} else {
pathMap.set(pathname, 0);
window.scrollTo(0, 0);
}
}, [pathname]);
useEffect(() => {
const fn = debounce(() => {
pathMap.set(pathname, window.scrollY);
}, 200);
window.addEventListener('scroll', fn);
return () => window.removeEventListener('scroll', fn);
}, [pathname]);
return null;
};
export default Index;
App.tsx
function App() {
return (
<Router>
<ScrollManager/>
<Switch>...</Switch>
</Router>
);
}
You can also use pathMap.size === 1 to determine if the user entered the app for the first time
I wound up using localStorage to track the scroll position - not sure this would handle all situations.
In this example, there's a Company page with a set of Stores, and each Store has a set of Display cases. I needed to track the scroll position of the display cases, so saved that to a 'storeScrollTop' key. There were 6 lines of code to add.
company.jsx:
// click on a store link
const handleClickStore = (evt) => {
window.localStorage.removeItem('storeScrollTop') // <-- reset scroll value
const storeId = evt.currentTarget.id
history.push(`/store/${storeId}`)
}
store.jsx:
// initialize store page
React.useEffect(() => {
// fetch displays
getStoreDisplays(storeId).then(objs => setObjs(objs)).then(() => {
// get the 'store' localstorage scrollvalue and scroll to it
const scrollTop = Number(window.localStorage.getItem('storeScrollTop') || '0')
const el = document.getElementsByClassName('contents')[0]
el.scrollTop = scrollTop
})
}, [storeId])
// click on a display link
const handleClickDisplay = (evt) => {
// save the scroll pos for return visit
const el = document.getElementsByClassName('contents')[0]
window.localStorage.setItem('storeScrollTop', String(el.scrollTop))
// goto the display
const displayId = evt.currentTarget.id
history.push(`/display/${displayId}`)
}
The trickiest part was figuring out which element had the correct scrollTop value - I had to inspect things in the console until I found it.
This component scroll to up if page is new but if page seen before restore the scroll.
scroll-to-top.tsx file:
import { useEffect, FC } from 'react';
import { useLocation } from 'react-router-dom';
const pathNameHistory = new Set<string>();
const Index: FC = (): null => {
const { pathname } = useLocation();
useEffect((): void => {
if (!pathNameHistory.has(pathname)) {
window.scrollTo(0, 0);
pathNameHistory.add(pathname);
}
}, [pathname]);
return null;
};
export default Index;
app.tsx file:
<BrowserRouter>
<ScrollToTop />
<App />
</BrowserRouter>
Use this library
https://www.npmjs.com/package/react-scroll-restoration
React Router does not provide out of the box support for scroll restoration and as it currently stands they won't either, because browsers are implementing some automatic scroll behavior