debounceTime not limiting api calls - reactjs

I have this code:
await commuteReportService.getAddresses(query).pipe(debounceTime(1000))
.subscribe((response: AddressesAPIResponse) => {
console.log('execute call', response);
});
However, in the network tab I still see that all the requests are being sent. This code is used in a autocomplete component where I want to limit the calls being sent.
I'm using rxjs 5.5, React and Typescript.

debounceTime is only applied to whatever comes after it, meaning it only applies to your 'subscribe' — not getAddresses(query), which is where I assume you're making calls.
Consider this minimal example:
// html
<input type="text" id="example" />
// js
const input = document.getElementById('example');
Rx.Observable
.fromEvent(input, 'keyup')
.map(v => {
console.log('called')
return v.target.value;
})
.debounceTime(500)
.subscribe(val => {
console.log(`Debounced: ${val}`);
});
Even though Debounced... is delayed, you'll still see called being logged to the console on every keystroke. If we change the order
// js
Rx.Observable
.fromEvent(input, 'keyup')
.debounceTime(500)
.map(v => {
console.log('called')
return v.target.value;
})
.subscribe(val => {
console.log(`Debounced: ${val}`);
});
Now both Debounced... and called are delayed (Here's the fiddle for the code above).

Related

How can I test all this logic inside setState callback parameter

I'm trying to test the following code. I'm using jest and react testing library. This is the firs time I've used setState like this. I solved my initial which was to avoid passing in the dependency of current state but I'm not sure how can I test this. Can someone please advise.
useEffect(() => {
setUsers(currentUsers => {
if(currentUsers === undefined) {
return userDataFromApi;
} else {
//Users already exist in state
const mergedUserData = currentUsers.map(existingUser => {
const matchedUser = userDataFromApi.find(user => user.name === existingUser.name);
if (matchedUser) {
existingUser.stats = user.stats;
}
return existingUser;
});
return mergedUserData;
}
});
}, [setUsers, userDataFromApi]);
This piece of code is implementation detail. React testing library enforces UI testing. You can read this article from Kent Dodds.
In your tests you can do the same thing as the user would do (fill a form, click etc.), and then check what the user should see or not see (maybe his name, his stats etc.).
And if you get data from your backend and you would like to test only the frontend, you can mock the answer of the backend.

Redux how I should handle update with API call

I have an application that triggers many update and I would like to know more about the best way to update the app properly.
In my app, I have 5 slots to fill with books (can be managed by drag and drop). When the app launches, the filled book for the user are loaded and are stored in the state.
Problem : when I update a book, like if I switch the position of 2 books in my list, I must do some operations to say "this book belongs here now and the other one belongs here now, switch!"
I feel like I'm doing some tedious actions because if I just return the whole data (get, after updating) from my API call and call the "load" function (as I do when I launch the app) I will not have to handle the update of the operation.
Plus, it could create bug If I'm loading correctly, but not updating correctly (if I miss position of a book for example)
The benefit I see in a functional update is that I only update the 2 books I need, instead of reload all of them again and again.
What way would be better? Should I get rid of those updates functions and just reload the data entirely? I think there could be also some libraries that cache it to only re-render modified books
Thanks you
Without code it is difficult to fully understand the problem but getting the data from the server has 2 advantages.
You are sure the ui shows the data as it is on the server
Your client code does not need to contain the logic of what needs to happen, the server has this logic. When the logic is refactored in some way they don't go out of sync.
Because of this I usually choose to get the data as is on the server.
One problem with fetching data based on user interaction is that fetching is async so the following can happen:
User does action A, request made for A, user Does action B, request made for B, B request resolves and UI is set to result of request B, request made for A resolves and UI is set to result of A.
So the order the user does the actions does not guarantee the order in which the requests are resolved.
To solve this you can use a helper that resolves only if it was last requested, in the example above when A request resolves the UI does not need to be set with anything because it has already been replaced with another request.
In the example below you can type search value, when the value is 1 character long it'll take 2 seconds to resolve so when you type ab the ab request will resolve before the a request. but because the function making the request is wrapped with the last helper when a resolves it'll will be rejected because it has been replaced with the newer request ab.
//constant to reject with when request is replaced with a
// more recent request
const REPLACED = {
message: 'replaced by more recent request',
};
//helper to resolve only last requested promise
const last = (fn) => {
const check = {};
return (...args) => {
const current = {};
check.current = current;
return Promise.resolve()
.then(() => fn(...args))
.then((result) => {
//see if current request is last request
if (check.current === current) {
return result;
}
//was not last request so reject
return Promise.reject(REPLACED);
});
};
};
const later = (howLong, value) =>
new Promise((resolve) =>
setTimeout(() => resolve(value), howLong)
);
const request = (value) =>
later(value.length === 1 ? 2000 : 10, value).then(
(result) => {
console.log('request resolved:', result);
return result;
}
);
const lastRequest = last(request);
const App = () => {
const [search, setSearch] = React.useState('');
const [result, setResult] = React.useState('');
React.useEffect(() => {
//if you use request instead of lastRequest here
// you see it will break, UI is updated as requests
// resolve without checking if it was the last request
lastRequest(search)
.then((result) => setResult(`result:${result}`))
.catch((err) => {
console.log(
'rejected with:',
err,
'for search:',
search
);
if (err !== REPLACED) {
//if the reject reason is not caused because request was
// replaced by a newer then reject this promise
return Promise.reject(err);
}
});
}, [search]);
return (
<div>
<label>
search
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
></input>
</label>
<div>{result}</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to wait for getDownloadURL to finish in my mapping function before updating my object array in react state?

getImages() {
const entries_copy = this.state.entries;
entries_copy.map(entry => {
storage.refFromURL(entry.sign_in_photo).getDownloadURL()
.then((url) => {
entry["inPhotoURL"] = url;
storage.refFromURL(entry.sign_out_photo).getDownloadURL()
.then((url) => {
entry["outPhotoURL"] = url;
});
}).catch((error) => {
// Handle any errors
});
});
this.setState({entries: entries_copy});
}
I'm trying to retrieve the download url for images and store them in my entry object inside my entries object array but the problem I'm facing right now is that the setState is called before the urls are retrieved and I have no idea how to wait for it to complete before setting the state. I have searched for similar problems but most of them are solved by executing it inside then() but for mine, I can't execute it inside then() because I have to wait for all the entries to be updated. I have only recently started using React for this project so I'm sorry if the answer is obvious.
This is because the code in asynchronous.
You should call setState inside the .then() function.
I would recommend you to read about Promises in Javascript. They are an important aspect of the language to master.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
In addition to the answer of #TomSlutsky, note that you need to correctly chain your promises and you should not forget to "always return results, otherwise callbacks won't catch the result of a previous promise".
So you need to do as follows:
storage.refFromURL(entry.sign_in_photo).getDownloadURL()
.then((url) => {
entry["inPhotoURL"] = url;
return storage.refFromURL(entry.sign_out_photo).getDownloadURL()
})
.then((url) => {
entry["outPhotoURL"] = url;
this.setState(...);
})
.catch((error) => {
// Handle any errors
});
Note also how the catch() method is called at the end of the chain, see the doc for more details (and possible other options).

Cypress e2e test without using wait function

I'm new in e2e testing with Cypress and I have a question.
I wrote a test and I want not to use Cypress wait() command in it.
it('Should modify checkbox state', () => {
login();
cy.visit(TabsSettings.SIZE_FORMATS).then(() => {
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]', { timeout: 10000 }).click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[1]')
.then(checkbox => checkbox).should('be.checked')
);
});
cy.get('span').contains('Change').click().then(() => {
cy.get('li').contains('Disable').click().then({ timeout: 5000 }, () => {
cy.wait(500);
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[1]')
.then(checkbox => checkbox[0]).should('be.checked')
);
});
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[18]').click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[2]')
.then(checkbox => checkbox[3]).should('not.checked')
);
});
});
cy.xpath('//span[contains(text(), "Disable Selected")]').click().then(() => {
cy.get('li').contains('Enable').click().then(() => {
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('not.checked')
);
});
});
cy.get('input[type=checkbox]').then(el => el[0].click()).then(() => {
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('be.checked')
);
}).then(() => {
cy.xpath('//i').then(x => x[1].click());
cy.get('input[type=checkbox]').should('not.checked');
});
cy.get('div[data-main-select=true]').then(list => list[1].click()).then(() => {
cy.xpath('(//li[contains(text(), "50")])[1]').click().then(() => {
cy.get('input[type=checkbox]').should(checkboxes => {
expect(checkboxes).to.have.length(51);
});
});
});
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click().then(() => {
cy.wait(150);
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('be.checked')
);
});
});
});
});
My problem is that if I not use that cy.wait(500) command, the test fails.
I googled a lot but I cannot find an answer for my problem.
The project is an React project, using Hooks. What may be caused the problem is that my component renders multiple times after that click() event. The page does not reload, but the main component rerenders several times. If this is the problem, how can I wait for finishing all renderings, and just after that continue the test, without using the wait function ?
You shouldn't need to use wait with time, but sometimes you may need to wait for a certain request to finish. I find it's the case with some AJAX requests. You can get it to wait for a specific request to finish using route definitions:
cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('messages/*', 'fixture:messages').as('getMessages')
// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')
// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['#getActivities', '#getMessages'])
// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')
Read more here: https://docs.cypress.io/guides/guides/network-requests.html#Waiting
Looking at it a bit further, you seem to be using a promise-style syntax unnecessarily.
Because Cypress has automatic retry on (most) commands, you can just call the commands sequentially without awaiting the result with .then(... constructs.
As an example, your code might be revised to something like this,
it('Should modify checkbox state', () => {
login();
cy.visit(TabsSettings.SIZE_FORMATS);
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]', {timeout: 10000 }).click();
// cy.xpath('(//input[#type="checkbox"])[1]').then(checkbox => checkbox).should('be.checked')
cy.get('input[#type="checkbox"]).eq(0).should('be.checked'); // NB eq() has zero-based indexing.
cy.get('span').contains('Change').click();
cy.get('li').contains('Disable').click();
// OR if you have to wait for the Disable button/link to appear, use this
cy.contains('li', 'Disable').click();
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click();
cy.get('input[#type="checkbox"]).eq(0).should('be.checked');
...
I'm not familiar with xpath syntax, so the translation is a guess, but it seems Cypress' commands are a little more readable, have a flatter layout, PLUS you get auto-retry built in, which removes the need to wait for specific periods.
If you want to add to your question a pseudocode translation of the xpath expressions, I'll give you the equivalent Cypress commands.

Using React to render flash messages from Express

I've searched around a lot but have been unable to find a simple way to get flash messages from Express and render them in React.
I need to access the data on my Express server, but what is the best way of storing this and passing it down to React? I was thinking of passing an object down when the React index.html file is rendered, but I'm not sure how I can access this data, or send the correct data when certain events happen, for example a user enters the wrong password.
I solved the issue.
I simply have a variable in my session called flash which is set to false by default.
In the correct part of the passport flow I redefine this to a string, depending on the error. I have a React action and reducer to get this data and if it's truthy, render it to the screen. When the component unmounts or the site is refreshed I reset it to false.
EDIT: I have found a better solution
1. In the passport middleware set an optional message if something goes wrong.
return done(null, false, { message: 'Email not found' });
2. In the login route send this information as a response.
router.post('/login', (req, res, next) => {
passport.authenticate('local-login', (e, user, info) => {
if(e) return next(e);
if(info) return res.send(info);
req.logIn(user, e => {
if(e) return next(e);
return res.send(user);
});
})(req, res, next);
});
3. Handle the submission and response in a Redux action generator. If the user authenticates, then the message property will be undefined.
const res = await axios.post('/auth/login', { email, password });
dispatch({
type: 'FLASH',
payload: res.data.message
});
4. In the reducer, the state will be either a string or false:
return action.payload || false;
5. Then it's a question of rendering the state to the screen. Another action can be sent when the component unmounts to reset the state.
Hope this helps someone else out there.
expressjs/flash will place an array of flash objects onto res.locals. Per the docs: https://github.com/expressjs/flash#reslocalsflash
res.locals.flash
An array of flash messages of the form:
{
"type": "info",
"message": "message"
}
From my understanding, anything placed on res.locals is available in the global scope. In other words, you should be able to do window.flash which should return an Array of flash objects.
So you would simply loop over the array as you would normally in JavaScript. That is just my guess.
const makeFlashElement = ({type, message}) => {
return (
<div>
<h1>message</h1>
<h2>type</h2>
</div>
)
}
for (message in flash) {
makeFlashElement(message)
// ...
}
Typically you'd return a JSON response which React can easily digest.
See Karl Taylor's comment.

Resources