I have a test that initiates a copy to clipboard. in ie11 that generates a dialog https://imgur.com/aeBZ71A
I can't clear it. Here is my code
fixture.only`Downloads`
.page`${page.page}`
.beforeEach(async (t) => {
await t
.maximizeWindow()
.setNativeDialogHandler((type, text, url) => {
if (type === 'confirm') { return false } return true
})
await page.loginAdmin()
})
.afterEach(async (t) => {
await t.setNativeDialogHandler(null)
})
But it won't clear the dialog. I have also tried
.setNativeDialogHandler(() => true)
but the dialog remains and the test times out
You can use the following approach for Clipboard interaction:
const overrideClipboardCopy = ClientFunction(() => {
const execCommand = document.execCommand;
document.execCommand = (action, ...args) => {
if (action === 'copy') {
//handle copy here
}
else
return execCommand.call(document, ...args);
}
});
See also: Allow to use HTML5 Clipboard API in tests
Related
I am trying to fetch data from Algolia database (index.search is similar to fetch) in useEffect,but then I find the order it execute is not the way I think.I console "queryNews1", "queryNews2", ..."queryNews6" in async function queryNews(), and I think they will sequentially print out in console(see image below). But I find that after queryNews2, it "jump out" of queryNews() but execute the code outside queryNews(), after console.log("5"), it go back to execute "queryNews3".
I guess it's an asychronous issue, so I wrap queryNews() inside an another async function const getData = async () => { await queryNews(keyword); }; and call getData(), but it's still execute in wrong way.Why and does anybody know how to fix that??
Sorry for my bad English!
mobile in the console is writing in articleState.map(() => { console.log("mobile"); return (); });
console results image
const [articleState, setArticles] = useState<ArticleType[]>([]);
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
getData();
}
}
const getData = async () => {
await queryNews(keyword);
};
getData()
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
Thanks to kim3er, that really help.
But simillar situation happened again when I scroll, it console.log("queryNews2"), and then it console "mobile", which means it render the component again, and then go back to queryNews() to finish execute the rest of the function?Why didn't it wait while I already put all the code in
getData().then(() => {
console.log("6");
window.addEventListener("wheel", scrollHandler);
console.log("7");
});
Thanks!!
(stack overflow suddenly said I can't embed image now so I paste an image link instead)
https://imgur.com/a/lDKEzxy
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
getData().then(() => {
console.log("6");
window.addEventListener("wheel", scrollHandler);
console.log("7");
});
}
}
const getData = async () => {
await queryNews(keyword);
};
getData().then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
You're calling getData() without awaiting it. Because of this, it'll run parallel to the top level useEffect code. This isn't an issue, if getData() is the last line of code in the function.
But if you do need that initial getData() to complete before hitting console.log("4");, you could do this:
getData()
.then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
From console.log("4"); will run after the call to getData().
Clarifier on async and .then()
With this function in mind:
const doSomething = async () => {
// Do something async
console.log('during');
});
The following:
const asyncFunc = async () => {
console.log('before');
await doSomething();
console.log('after');
});
Is equivalent to:
const asyncFunc = () => {
console.log('before');
doSomething()
.then(() => {
console.log('after');
});
});
Either will return:
before
during
after
However, if you used:
const syncFunc =() => {
console.log('before');
doSomething();
console.log('after');
});
Becasue I have not awaited doSomething(), I have created a race condition, whereby during and after could be returned in a different order depending on how long it took the async code to complete. Because the syncFunc script will continue running as soon as doSomething has been called (but crucially, has not finished).
So you could get:
before
after
during
Wrapping await queryNews(keyword); in another function called getData() does not make the function synchronous, it just means that you don't have to keep typing in the keyword parameter. You still need to await getData(), in order to ensure completion before continuing.
#kim3er
Thank you for your detailed answer, and sorry for my late response.
According to your explanation about difference between async/await and .then(),
I found that I didn't await getData() in scrollHandler(), so I modified my code again, I call queryNews(keyword) this time instead, but then something weird still exist.
The console.log sequence is right when first load(No1~10 in image below),and then when I scroll, I call scrollHandler(), and inside scrollHandler, await queryNews() is executed, but then again,it console to "queryNews2", and then it "jump out" of queryNews() to render component again I guess, because the word "mobile" in JSX tag is console before "queryNews3"(No13~18 in image below).
I use await queryNews(keyword) in scollHandler() this time, but it still has wrong console sequence.Why?Does it has anything to do with setState inside queryNews()?Because as far as I know, setState will trigger a component re-render?
Thank you for answer my question patiently.
console image
useEffect(() => {
console.log("1");
if (windowResized === "large" || windowResized === undefined) return;
let isFetching = false;
let isPaging = true;
let paging = 0;
console.log("2");
async function queryNews(input: string) {
console.log("queryNews1");
isFetching = true;
setIsLoading(true);
setSearchState(true);
setPageOnLoad(true);
console.log("queryNews2");
const resp = await index.search(`${input}`, {
page: paging,
});
console.log("queryNews3");
const hits = resp?.hits as ArticleType[];
setTotalArticle(resp?.nbHits);
console.log("queryNews4");
setArticles((prev) => [...prev, ...hits]);
console.log("queryNews5");
setIsLoading(false);
console.log("queryNews6");
paging = paging + 1;
if (paging === resp?.nbPages) {
isPaging = false;
setScrolling(true);
return;
}
console.log("queryNews7");
isFetching = false;
setSearchState(false);
setPageOnLoad(false);
console.log("queryNews8");
}
console.log("3");
async function scrollHandler(e: WheelEvent) {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - 100
) {
if (isFetching || !isPaging) return;
console.log("scrollHandler");
await queryNews(keyword);
console.log("end scrollHandler");
}
}
queryNews(keyword).then(() => {
console.log("4");
window.addEventListener("wheel", scrollHandler);
console.log("5");
});
return () => {
window.removeEventListener("wheel", scrollHandler);
};
}, [keyword, setSearchState, windowResized]);
I am new to React and working on a drag and drop feature and in that I have to send a api request to backend on the drag end and also I need to update the state.
To achieve this I am using redux-toolkit createAsyncThunk.
On drag end action is dispatched in which backend api is called and then in redux store, inside extraReducres the state is updated. Updating state in this way is causing lag in state update and causing very poor user experience.
Refer the image for bug:
Sample code:
// Component code
const onDragEnd = () => {
//reordering logic
dispatch(updateDragNDrop(newOrderObjArray));
}
//Async Thunk Code
export const updateDragNDrop = createAsyncThunk(
"todoBlockSlice/updateStateAfterDragNDrop",
async (sections: any) => {
let sectionsArr = [ sections.src, sections.dest]
await axios.post("http://localhost:8080/updateDnd", sectionsArr);
return sections;
}
);
// Extra Reducers Code
builder.addCase(updateDragNDrop.fulfilled, (state, action) => {
let src: TodoSectionType = action.payload.src;
let dest: TodoSectionType = action.payload.dest;
let newSectionsState: TodoSectionType[] = state.sections.map(
(sec: TodoSectionType) => {
if (sec.sectionType === src.sectionType) {
let section: TodoSectionType = {
...src,
};
return section;
} else if (dest !== null && sec.sectionType === dest.sectionType) {
let section: TodoSectionType = {
...dest,
};
return section;
}
return sec;
}
);
state.sections = newSectionsState;
});
I would like to be able to send word doc/pdf files via messaging in my react native app using react native gifted chat. I have had a look at a few links which suggests using the renderActions() function in react-native-gifted-chat but it does not specify how I can implement this. Do you know how I can implement this function? Would I need to import a package like document picker or file picker in the function? If so, how can I use this? I'm fairly new to react native. Can someone please help here?
Here is what I have so far in my renderActions() method:
renderActions() {
return(
<Actions
{...props}
options={{
['Document']: async (props) => {
try {
const result = await DocumentPicker.pick({
type: [DocumentPicker.types.doc || DocumentPicker.types.docx || DocumentPicker.types.pdf],
});
console.log("resulting file: "+result);
console.log("string result? "+JSON.stringify(result));
} catch(e){
if(DocumentPicker.isCancel(e)){
console.log("User cancelled!")
} else {
throw e;
}
}
},
['Cancel']: (props) => {console.log("cancel")}
}}
icon={() => (
<Ionicons
name={'add'}
size={28}
color={'#0077ff'}
style={{left:0, bottom:0}}
/>
)}
onSend={args => console.log(args)}
/>
)
}
Which produces:
I have managed to get the file object. Does anyone know how I can append this doc file object to the messages in gifted chat once selected? Can someone please help? How can I display in the chat box and then send the file?
Thanks.
The link https://github.com/FaridSafi/react-native-gifted-chat/issues/2111 mentions to to add parameters to the message object. For example you have this message object:
const newMessage = {
_id: data.send_at,
text: data.messagetext,
createdAt: data.send_at,
(...),
file_type: data?.file_type,
file_id: data?.file_id,
}
Then render a custom view:
const renderCustomView = (props) => {
if (props?.currentMessage?.file_type) {
(...)
}
else {
(...)
}
}
Can someone please help on where I would need to create the messages object as well as what I would need to put inside the renderCustomView function? I am really not too sure on what needs to be done.
function renderActions(props) {
let selectFile = async () => {
//Opening Document Picker to select one file
try {
const res = await DocumentPicker.pick({
//Provide which type of file you want user to pick
type: [DocumentPicker.types.pdf],
//There can me more options as well
// DocumentPicker.types.allFiles
// DocumentPicker.types.images
// DocumentPicker.types.plainText
// DocumentPicker.types.audio
// DocumentPicker.types.pdf
});
//Printing the log realted to the file
console.log('res : ' + JSON.stringify(res));
props.onSend({pdf:res.uri,file_type:'pdf'});
//Setting the state to show single file attributes
singleFile = res;
// setSingleFile(res);
} catch (err) {
singleFile = null;
// setSingleFile(null);
//Handling any exception (If any)
if (DocumentPicker.isCancel(err)) {
//If user canceled the document selection
alert('Canceled from single doc picker');
} else {
//For Unknown Error
alert('Unknown Error: ' + JSON.stringify(err));
throw err;
}
}
};
const handlePicker = () => {
// console.log('edit');
ImagePicker.showImagePicker({}, (response) => {
// console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
setAvatar({uri: response.uri});
console.log(response.uri);
props.onSend({image:response.uri});
// onSend([{"_id": "f3fda0e8-d860-46ef-ac72-0c02b8ea7ca9", "createdAt": new Date(), "image": response.uri, "user": {"_id": 1}}])
return response.uri
// here we can call a API to upload image on server
}
return avatar;
});
};
return (
<Actions
{...props}
options={{
['Send Image']: () => handlePicker(),
['Send Files']: () => selectFile(),
}}
icon={() => (
<Icon name='attachment' size={28} />
)}
// onSend={onSend}
/>
)}
in custom view :
export default class CustomView extends React.Component {
renderPdf() {
return (
<TouchableOpacity style=
{[styles.container,this.props.containerStyle]} >
<Image
{...this.props.imageProps}
style={[styles.image, this.props.imageStyle]}
source ={{
uri:""
}}
/>
</TouchableOpacity>
);
}
render() {
if (this.props.currentMessage.file_type == 'pdf') {
return this.renderPdf();
} else if (this.props.currentMessage.template &&
this.props.currentMessage.template != 'none') {
return this.renderHtml();
}
return null;
}
}
I have this custom hook which fetches the query.me data from graphql. The console.log statement shows that this hook is running a number of times on page load, but only 1 of those console.logs() contains actual data.
import { useCustomQuery } from '../api-client';
export const useMe = () => {
const { data, isLoading, error } = useCustomQuery({
query: async (query) => {
return getFields(query.me, 'account_id', 'role', 'profile_id');
},
});
console.log(data ? data.account_id : 'empty');
return { isLoading, error, me: data };
};
I then have this other hook which is supposed to use the id's from the above hook to fetch more data from the server.
export const useActivityList = () => {
const { me, error } = useMe();
const criteria = { assignment: { uuid: { _eq: me.profile_id } } } as appointment_bool_exp;
const query = useQuery({
prepare({ prepass, query }) {
prepass(
query.appointment({ where: criteria }),
'scheduled_at',
'first_name',
'last_name',
);
},
suspense: true,
});
const activityList = query.appointment({ where: criteria });
return {
activityList,
isLoading: query.$state.isLoading,
};
};
The problem I am facing is that the second hook seems to call the first hook when me is still undefined, thus erroring out. How do I configure this, so that I only access the me when the values are populated?
I am bad with async stuff...
In the second hook do an early return if the required data is not available.
export const useActivityList = () => {
const { me, error } = useMe();
if (!me) {
return null;
// or another pattern that you may find useful is to set a flag to indicate that this query is idle e.g.
// idle = true;
}
const criteria = { assignment: { uuid: { _eq: me.profile_id } } } as appointment_bool_exp;
...
I would like to detect when the user leaves the page Next JS. I count 3 ways of leaving a page:
by clicking on a link
by doing an action that triggers router.back, router.push, etc...
by closing the tab (i.e. when beforeunload event is fired
Being able to detect when a page is leaved is very helpful for example, alerting the user some changes have not been saved yet.
I would like something like:
router.beforeLeavingPage(() => {
// my callback
})
I use 'next/router' like NextJs Page for disconnect a socket
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyPage() {
const router = useRouter()
useEffect(() => {
const exitingFunction = () => {
console.log('exiting...');
};
router.events.on('routeChangeStart', exitingFunction );
return () => {
console.log('unmounting component...');
router.events.off('routeChangeStart', exitingFunction);
};
}, []);
return <>My Page</>
}
router.beforePopState is great for browser back button but not for <Link>s on the page.
Solution found here: https://github.com/vercel/next.js/issues/2694#issuecomment-732990201
... Here is a version with this approach, for anyone who gets to this page
looking for another solution. Note, I have adapted it a bit further
for my requirements.
// prompt the user if they try and leave with unsaved changes
useEffect(() => {
const warningText =
'You have unsaved changes - are you sure you wish to leave this page?';
const handleWindowClose = (e: BeforeUnloadEvent) => {
if (!unsavedChanges) return;
e.preventDefault();
return (e.returnValue = warningText);
};
const handleBrowseAway = () => {
if (!unsavedChanges) return;
if (window.confirm(warningText)) return;
router.events.emit('routeChangeError');
throw 'routeChange aborted.';
};
window.addEventListener('beforeunload', handleWindowClose);
router.events.on('routeChangeStart', handleBrowseAway);
return () => {
window.removeEventListener('beforeunload', handleWindowClose);
router.events.off('routeChangeStart', handleBrowseAway);
};
}, [unsavedChanges]);
So far, it seems to work pretty reliably.
Alternatively you can add an onClick to all the <Link>s yourself.
You can use router.beforePopState check here for examples
I saw two things when coding it :
Knowing when nextjs router would be activated
Knowing when specific browser event would happen
I did a hook that way. It triggers if next router is used, or if there is a classic browser event (closing tab, refreshing)
import SingletonRouter, { Router } from 'next/router';
export function usePreventUserFromErasingContent(shouldPreventLeaving) {
const stringToDisplay = 'Do you want to save before leaving the page ?';
useEffect(() => {
// Prevents tab quit / tab refresh
if (shouldPreventLeaving) {
// Adding window alert if the shop quits without saving
window.onbeforeunload = function () {
return stringToDisplay;
};
} else {
window.onbeforeunload = () => {};
}
if (shouldPreventLeaving) {
// Prevents next routing
SingletonRouter.router.change = (...args) => {
if (confirm(stringToDisplay)) {
return Router.prototype.change.apply(SingletonRouter.router, args);
} else {
return new Promise((resolve, reject) => resolve(false));
}
};
}
return () => {
delete SingletonRouter.router.change;
};
}, [shouldPreventLeaving]);
}
You just have to call your hook in the component you want to cover :
usePreventUserFromErasingContent(isThereModificationNotSaved);
This a boolean I created with useState and edit when needed. This way, it only triggers when needed.
You can use default web api's eventhandler in your react page or component.
if (process.browser) {
window.onbeforeunload = () => {
// your callback
}
}
Browsers heavily restrict permissions and features but this works:
window.confirm: for next.js router event
beforeunload: for broswer reload, closing tab or navigating away
import { useRouter } from 'next/router'
const MyComponent = () => {
const router = useRouter()
const unsavedChanges = true
const warningText =
'You have unsaved changes - are you sure you wish to leave this page?'
useEffect(() => {
const handleWindowClose = (e) => {
if (!unsavedChanges) return
e.preventDefault()
return (e.returnValue = warningText)
}
const handleBrowseAway = () => {
if (!unsavedChanges) return
if (window.confirm(warningText)) return
router.events.emit('routeChangeError')
throw 'routeChange aborted.'
}
window.addEventListener('beforeunload', handleWindowClose)
router.events.on('routeChangeStart', handleBrowseAway)
return () => {
window.removeEventListener('beforeunload', handleWindowClose)
router.events.off('routeChangeStart', handleBrowseAway)
}
}, [unsavedChanges])
}
export default MyComponent
Credit to this article
this worked for me in next-router / react-FC
add router event handler
add onBeforeUnload event handler
unload them when component unmounted
https://github.com/vercel/next.js/issues/2476#issuecomment-563190607
You can use the react-use npm package
import { useEffect } from "react";
import Router from "next/router";
import { useBeforeUnload } from "react-use";
export const useLeavePageConfirm = (
isConfirm = true,
message = "Are you sure want to leave this page?"
) => {
useBeforeUnload(isConfirm, message);
useEffect(() => {
const handler = () => {
if (isConfirm && !window.confirm(message)) {
throw "Route Canceled";
}
};
Router.events.on("routeChangeStart", handler);
return () => {
Router.events.off("routeChangeStart", handler);
};
}, [isConfirm, message]);
};