I am trying to show a "spinner loader" when a user clicks a button that fires a function which takes some time, so the user knows that stuff is being done in the back-end. I am writing in VueJS the front-end. The spinner is being shown only after the function has finished its job, which is something I do not want as my goal is that the spinner is shown as long as the function is run and the user wait.
// Unlock is a prompt page that contains a "Success" button and a "Cancel" button
<unlock v-model="password" #cancel="cancel" #success="add"/>
<icon type="spinner" class="spin" v-if="loading"></icon>
methods: {
async add() {
this.loading = true
this.error = ''
await this.$store.dispatch('DO_SOMETHING', { users: this.selected_users, password: this.password })
this.loading = false
this.$store.dispatch('TOGGLE_PROMPT', '')
}
...
...
}
I expected the spinner to show up as soon as the function is fired up, until it finishes when another page loads either way. I would like the spinner to be shown as long as we wait for the await this.$store.dispatch('DO_SOMETHING') to execute completely. Problem is, it is shown only after the whole function gets finished.
I tried playing around with the async and await, no results. Is it possible to show the element in HTML-VueJS directly, or this is something that just we cannot do it through Javascript?
Thank you in advance.
EDIT:
The action
DO_SOMETHING: ({ commit, dispatch, state }, { users, password }) => {
commit('SET_LOADING', true)
const results = JSON.stringify(users.map( x => {
if (...) delete ...
if (...) delete ...
return x
}))
API.addUser(results, password || state.password)
// refresh page
return dispatch('FETCH_PAGE')
},
The API.addUser, makes a call to the backend, where a Java function is run that makes the modifications in the database.
EDIT v2:
I added the following console.log()
console.log("1")
await this.$store.dispatch('DO_SOMETHING', { users: this.selected_users, password: this.password })
console.log("6")
console.log("2")
API.addUsers(results, password || state.password)
console.log("5")
The below is in the API/addUsers() function.
console.log("3")
const results = JSON.parse(window.UserbaseApplication.addUsers(users, password))
console.log("4")
As expected, the results are:
1
2
3
4
5
6
So the function is awaiting the return of the API as expected.
Not sure about Vue, but in vanilla JS:
Lets say you have a user press a button, which calls a function that runs some computation. During the computation, you have a loader ready to go.
const button = document.querySelector(".mybutton")
button.addEventListener('click', () => {
const loader = document.querySelector(".myloader");
loader.style.display = "inline-block";
// we are assuming the loader covers the whole width & height, with a black filter in the background
// therefore, we don't have to hide other elements
myfunction(...).then(
res => {
// hide the loader after successful promise call
loader.style.display = "none";
});
});
This example assumes that your function returns a promise
Same can be achieved with async/await
const myfunction = async function() {
const res = await .... some computation
return res
}
const button = document.querySelector(".mybutton")
button.addEventListener('click', () => {
const loader = document.querySelector(".myloader");
loader.style.display = "inline-block";
// we are assuming the loader covers the whole width & height, with a black filter in the background
// therefore, we don't have to hide other elements
const result = myfunction();
loader.style.display="none";
});
Related
I want to pass the watch time of a video the user has seen when user closes the page,reload the page or navigate to another page. I am using visibilityChange event for this. When i try to navigate to another page, the api call runs perfectly. But the data i am sending to the api is not updated correctly. I am going to provide the code and the output below so you can understand perfectly what my problem is.
useEffect(async () => {
const x = 0;
console.log("use effect is run number ::::", x + 1);
window.addEventListener("visibilitychange", sendViewTime);
return () => {
window.removeEventListener("visibilitychange", sendViewTime);
};
}, []);
I have added the event listener in the useEffect.
the sendViewTime method is the method i want to call on visibility change event. This Method is working perfectly but for some reason the params are not updated even though i have set their states in their relavant hooks.
const sendViewTime = async () => {
if (document.visibilityState === "hidden") {
console.log("the document is hidden");
const value = localStorage.getItem("jwt");
const initialValue = JSON.parse(value);
console.log("the send View Time is :::", played_time);
const params = {
video_id: video_id_url,
viewTime: played_time,
MET: MET_value,
weight: "",
};
console.log("params are :::", params);
await setEffort(params, initialValue).then((res) => {
console.log("set effort api response is ::: ", res);
});
} else {
console.log("the document is back online");
}
};
//This onProgress prop is from react Player. Here i am updating the state of video progress.
onProgress={(time) => {
console.log("the time is :::", time);
const time_1 = Math.round(time.playedSeconds);
const time_2 = JSON.stringify(time_1);
setPlayed_time(time_2);
console.log("the played time is :::", played_time);
}}
//OUTPUT
// the document is hidden.
// the send View Time is :::
//params are ::: {video_id: '23', viewTime: '', MET: undefined, weight: ''}
//set effort api response is ::: {status: 200, message: 'Success', data: {…}, time: '2.743 s'}
//the document is back online
Never mind guys. I found the solution. It seems that i have to pass played_time and met value as a prop to the useEffect.If you want to know how useEffect works please visit this link. In general is it better to use one or many useEffect hooks in a single component?.
I have an application in react native where i'm developing a search feature like Instagram.
It is like if user stop typing show him his query result.
my current approach is messing up redux. And sometimes it returns same element multiple times or sometime random elements which are irrelevant of that query.
right now. I'm calling search api immediately as use start typing in searchbar.
here is code below of my component.
import { getSearchDataApi } from "../../api/search/search";
import { clearSearchData, setSearchData } from "../../redux/action/search";
const SearchScreen =(props)=>{
const [autoFocus,setAutoFocus] = useState(true)
const [keyWord,setKeyWord] = useState(null)
const [isLoading,setIsLoading] = useState(false)
const [isError,setIsError] = useState(false)
const [pageNumber,setPageNumber] = useState(1)
const [loadMore,setLoadMore] = useState(true)
const loadMoreDataFunc =()=>{
if (pageNumber <= props.totalSearchPage) {
setPageNumber(pageNumber+1)
}
else {
setLoadMore(false)
}
}
const searchData = async(keyWord)=>{
console.log(keyWord,pageNumber)
try {
setIsLoading(true)
var searchResponse = await getSearchDataApi(keyWord,pageNumber)
props.setSearchData(searchResponse.data)
setIsLoading(false)
}
catch (e) {
setIsError(true)
console.log("Error --- ", e.response.data.message)
showMessage({
message: e.response.data.message,
type: "danger",
});
}
}
return (
<View>
....
</View>
)
}
const mapStateToProps = (state)=>({
searchData: state.searchReducer.searchData,
totalSearchPage: state.searchReducer.totalSearchPage,
})
export default connect(mapStateToProps,{setSearchData,clearSearchData})(SearchScreen);
I will really every thankful to someone how can help me in fixing. Appreciation in advance!
GOAL :
The goal that i want to achieve is when user stop typing then i call searchAPI with the keyword he/she entered in searchBar that's all.
I have also tried setTimeOut but that made things more worse.
The best solution to your problem is to debounce the state variable that is responsible for the user input. This way, you can use the effect hook to watch for changes on the debounced variable, and call the search API if/when conditions for the search API variables are met.
Well, I have put some effort to solve it with setTimeout once again and i have done it by following code of snippet.
useEffect(()=>{
setPageNumber(1)
props.clearSearchData()
const delayDebounceFn = setTimeout(() => {
console.log(keyWord)
if (keyWord) {
searchData(keyWord)
}
}, 500)
return () => clearTimeout(delayDebounceFn)
},[keyWord])
You can use a setInterval to create a countDown starting from 2 to 0, or 3 to 0, put it a state.
whenever user types, onChange is called, the from the callback you reset the countDown.
using useEffect with the countDown as dependency, you can open the search result whenever the countdown reaches 0. (which means the user hasn't typed anything since 2s ago)
this might help for creating the countdown https://blog.greenroots.info/how-to-create-a-countdown-timer-using-react-hooks
I currently have a unit test that will mock the click of a button. I've recently added a debounce function around it:
import * as lodash from 'lodash';
//...bunch of code in between
const buttonChangerTrue= lodash.debounce(() => buttonChanger(true, row), 500);
This click of the button will cause a change in the UI which will basically change the icon color (assuming the end user doesn't rapidly click it).
For the sake of brevity, I've removed the excess code for the test
this.when.push({
beforeEachRender: () => {
count++;
jest.useFakeTimers();
if (count === 1) {
this.mockHttpRequests();
} else {
this.mockHttpRequestsReversed('buttonPressed');
}
},
describe: 'When an unsaved search is ran and an agent is pinned',
then: async () => {
test('Then a note should be created', async () => {
const button = tableBody.children[1].children[1].children[0].children[0];
const stickyNoteBeforePinning =
this.getById(document.body, 'notes-69fc105ad2c94edf16efb1f4de125c38093aefe9') as HTMLElement;
if (!button ) {
throw 'button not found';
}
await wait(() => {
fireEvent.click(button);
});
jest.runTimersToTime(1000);
const stickyNoteAfterPinning =
this.getById(document.body, 'notes-ec9c2a3041a18a4a7d8d8b4943292cb8aa92a2f5') as HTMLElement;
expect(stickyNoteBeforePinning).toHaveAttribute('class', 'lead material-icons text-light');
expect(stickyNoteBeforePinning).not.toBe(stickyNoteAfterPinning);
expect(stickyNoteAfterPinning).toHaveAttribute('class', 'lead material-icons text-pink'); // Fails here
});
},
});
Please let me know if you need more information but this click of a button does make an API call, I've mocked the call in the test as well. When I remove the debounce function and run it as normal -- it passes the test just fine. I've tried a number of things like jest.useFakeTimers(); in the beforeEachRender portion and calling jest.runTimersToTime(1000); right after it. I've also tried using jest.advanceTimersByTime(500); right after the click too. Nothing seems to be working.
Edit: I ended up removing the tests for now. I read that Jest does have a version that can take into account something like jest.useFakeTimers('modern'); and that will simulate the debouncing. Will report back if I can get results on that.
You need to use useFakeTimers() from sinon
Here its an example of use:
import * as sinon from 'sinon';
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
test('debounce', () => {
// wait 1000ms
clock.tick(1000);
func() // Execute your function.
expect(func).toHaveBeenCalledTimes(1); // func called
});
Testing a form built with Vue using Jest for unit tests. Among the elements I have a reset button (type=reset), which works fine and once clicked it removes all the values already introduced.
However, when unit testing, the button click doesn't seem to clear the values. I don't have a handler for the click, just using the default reset function of the form.
I've also tried using wrapper.emmited('reset'); to no avail, and wrapper.emmitedByOrder(); returns an empty array.
How do I test that the reset button is generated correctly and works as expected?
test('Assert Form Components', async () => {
const wrapper = mount(FormElement, {
propsData: {
message: sampleJSON.formJSON
}
})
let resetBtn = wrapper.find('.form-reset');
let requiredInput = wrapper.find('.required-input');
....
requiredInput.setValue('test');
expect(requiredInput.element).toHaveValue('test'); //This passes
await resetBtn.trigger('click');
expect(requiredInput.element).not.toHaveValue('test') //This fails
....
Apparently there were two things missing. First, as #AlexMA suggested, allowing for another tick to let the DOM settle. Second, I needed to attach the wrapper to the DOM. The final code look something like this:
test('Assert Form Components', async () => {
const wrapper = mount(FormElement, {
propsData: {
message: sampleJSON.formJSON
},
attachTo: document.body
})
let resetBtn = wrapper.find('.form-reset');
let requiredInput = wrapper.find('.required-input');
....
requiredInput.setValue('test');
expect(requiredInput.element).toHaveValue('test'); //This passes
await resetBtn.trigger('click');
await wrapper.vm.$nextTick()
expect(requiredInput.element).not.toHaveValue('test') //This works now!
....
I have a function where I'd like to set the successMessage to "success" and then after 5 seconds I'd like to set successMessage to an empty string again, but I haven't been able to figure it out.
Preferably I'd like to create a helper function where I can pass in 3 variables - function to run before (setSuccessMessage('Success!')), function to run after (setSuccessMessage('')) and the delay (5000ms)
How can I achieve this?
The Simpliest solution that runs timeout whenever the message changes to anything but empty string. Bear in mind that this example is written by hand without any testing ;)
const [successMessage, setSuccessMessage] = useState('');
const timerRef = useRef(null);
const resetMessage = () => {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setSuccessMessage(''), 5000)
}
useEffect(() => {
if (successMessage !== '') resetMessage();
}, [successMessage, resetMessage]);