React App - Issues Calling 3rd Party API on First Try - reactjs

I have a react app that lets a user choose from LIs to get search results from a 3rd party API based on which LI they choose. The LI has a click handler that, as part of it, makes an API request to the 3rd party. The first time they click the LI, the value is null, even though it should be a piece of the React state. This is part of the click handler function:
let choice = event.currentTarget.innerText;
this.setState({inputVal: choice});
let urlquery = this.state.inputVal
console.log(urlquery);
The first time that LI is clicked, the console.log returns null. The second time you click it, you get back the correct value. Any suggestions on how to fix this?
Thanks

The state has not been updated when your let urlquery = this.state.inputVal line is run. Calls to setState(...) are asynchronous. Reference the react docs regarding setState(...) here: https://reactjs.org/docs/react-component.html#setstate
Your options to deal with this are to use the second parameter of setState(...), which is a callback which runs after setState has actually updated the state, or using componentDidUpdate. Both are mentioned in the docs referenced above.

Related

How to avoid letting an async thunk that is no longer useful (the app is not expecting it anymore) from being able to update the state?

I have the following pattern on my single page app (React + Redux).
It runs every time a load a page on the app. User navigates to a specific page, and the loadPageThunk is dispatched. The initial state of the page shows a spinner to the user. This is used for example, in a blogpost page.
That thunk will get some async data (the blogpost), and then will show the page with that data.
It works fine. When the user navigates away from the page. A useEffect dispatches a RESET action to reset the state back to its initial value.
My question is:
What if the async call takes too long to complete and the user navigates away? It will create a problem because now there's a pending promise that will complete in an unexpected time. How can I prevent that completion from updating my state?
Imagine the following steps for an async call that is taking 10 seconds to complete:
#### FIRST PAGE LOAD ####
USER VISITS blog/slug-1
loadPageThunk() IS DISPATCHED
blogPost1 STARTS GETTING FETCHED (WILL TAKE 10 SECONDS)
USER NAVIGATES AWAY
#### SECOND PAGE LOAD ####
USER VISITS blog/slug-2
blogPost2 STARTS GETTING FETCHED (WILL TAKE 10 SECONDS)
USER IS STILL SEEING SPINNER
blogPost1 (FROM THE PREVIOUS VISIT) HAS COMPLETE AND WILL UPDATE THE STATE
USER NOW SEES blog/slug-2 WITH THE DATA FROM blogPost1 WHICH IS AN ERROR
blogPost2 WILL EVENTUALLY COMPLETE AND USER WILL SEE A CONTENT FLICKER ON THE PAGE
QUESTION
How can I avoid pending promises that are no longer useful from being able to update the state?
This problem is not currently happening in my app, but I think that a good design should account for that.
Should I add an ID for my LOAD_PAGE cycle, so I can check the ID of the current cycle before allowing callbacks / async code from updating the state when IDs don't match? How do people usually handle this?
Personally I store blog data as entities (posts, comments, etc.) keyed by id and collections. The collection is just the array of post ids on a particular page.
For example,
{
entities: {
posts: {
1: {...},
2: {...}
},
comments: {
123: {...},
999: {...}
}
},
collections: {
"blog/slug-1": [99,98,97...],
"blog/slug-2": [89,88,87...],
}
}
This sort of structure means that every page can save its data in the correct place regardless of whether it is the current page or not. It also means that every page can select its own data and can see whether that data already exists in the state.
The promise returned by createAsyncThunk has an abort() method attached which can be used to 'cancel' the promise. See canceling while running. You can call abort() in your cleanup to prevent the thunk from being fulfilled.
In your reducers, if you are handling the rejected case for your thunk, then you can add an exception for cases where the error name is AbortError to do nothing instead.
To expand a bit about your specific situation: a good rule of thumb is that if you find yourself 'resetting' state when you unmount the component, then it should have just been local component state in the first place.

How to hide or remove an element onClick in React?

I am trying to hide an element 'GorillaSurfIn' after I click on it.
But also it should fire the 'setShouldGorillaSurfOut' to 'true'. The second part works, but after I added this function:
function hideGorillaSurfIn() {
let element = document.getElementById('gorilla-surf-in');
ReactDOM.findDOMNode(element).style.display =
this.state.isClicked ? 'grid' : 'none';
}
After I click, the code falls apart.
Once I click, the element should be hidden/removed till the next time the App restarts.
Here is the Code Sandbox Link for further explanation.
I am open to any solutions, but also explanations please, as I am still fresh in learning React.
I have changed your code a bit to make it work. You can make further changes according to your need. A few things that I would like to add: -
You should avoid using findDOMNode (in most cases refs can solve your problem) as there are certain drawbacks associated with findDOMNode, such as the react's documentation states "findDOMNode cannot be used with functional components".
I've used refs (forward ref in this case) to make it work.
GorillaSurfIn was called twice, so there were two Gorilla gifs on the screen with same IDs. Not sure if that was the intended behaviour but each element should have unique ID.
Check out the code sandbox.

ReactJS Function Components object updates on second time

I have a login page(Functional Component), When a user tries to login without entering the required fields, I have to show the error message(ex:"Email is required"). When an invalid field exists, I shouldn't make the API call.
But, without entering fields, when I click on login button, API call is done. Again clicking on login button stops the API call.
I have made this demo - https://stackblitz.com/edit/react-pd6vvk?file=login.js which explains the issue.
Steps to follow:
1.Click on login without filling any text values. You can see "API request made" statement when fields are invalid.
2.Again click on login button, "API Request stopped" statement is displayed now.
I am new to React and I don't know, the reason and the solution to fix this issue.
Can somebody please help me out?
Thank you,
Abhilash
Because, setValidFields has async behaviour :
validateLoginForm(); //<--- inside this setValidFieldsis async
console.log(validFields);
if (isFormValid()) { //<---- so,this will still have true value ( means not updated )
// inside validateLoginForm()
// this state change won,t be reflected immediately
setValidFields(validFields => ({
...validFields,
[key]: error.length == 0
}));
I have made few changes as per your code structure, as long as I know you will need useEffect also as shown in working demo.
WORKING DEMO

Clicking navigation button in Puppeteer returns null response? How to see which function is being clicked?

I'm trying to test clicking a button on a registration app to make sure that the page correctly navigates to the right page.
When I console.log out response in the code it returns an array that looks like this
[undefined, null]
However when I take a screenshot of the page or check the url, the click and navigation worked. It is on the correct page.
I don't understand why this is returning undefined/null. Also, I could not figure out how to test which function was being called when you click on the button.
I wanted to be able to see the actual function that is being called.
I'm using AngularJS 1.6, Mocha, Puppeteer 1.11, and Node 6.4.0
I'm also a junior dev so it could be something simple that I just didn't understand, please help!
it('should rederict to guest_sms_code state when clicking \'I have a guest code\'', async (function () {
var response = await (Promise.all([
page.waitForNavigation({waitUntil: 'domcontentloaded'}),
page.click('[ng-click="enterSmsCode()"]'),
]));
var url = await (page.url());
if (url.indexOf('guest_sms_code') === -1) {
assert.ok(false, 'The URL does not contain \'guest_sms_code\'')
}
}))
I'm not convinced that you can tell which method is called from a UI test. That's something that a JavaScript unit test can prove using, say, something like Mocha or Jest to test your codes behaviour. However an automated UI test, from my experience, won't tell you that. After all, you're trying to hide the inner workings of your app from an external user, right?
As for why the Promise.all([]) call is returning [undefined, null], well I can at least help you with that.
Firstly, you should have a look at the API documentation for both methods you've invoked in your test:
waitForNavigation docs: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions
click docs: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageclickselector-options
In the case of waitForNavigation the response actually returns a Promise which may or may not have a Response value. Whether any data is returned with that Response is dependent on the navigation that occurs in the UI. In your case, you're seeing that undefined value in your array to indicate that there has not been any Response data returned from the navigation.
For the click method, this also just returns a resolved or rejected Promise with no data of any kind returned. So, naturally, you've no actual value returned here at all hence the null value being pushed to the array.

ReactJS issue on my test app

So, I've been working through my first ReactJS app. Just a simple form where you type in a movie name and it fetches the data from IMDB and adds them as a module on the page. That's all working fine.
However each movie module also had a remove button which should remove that particular module and trigger a re-render. That's not working great as no matter which button you click it always removes the last movie module added rather than the one you're clicking on.
App:
http://lukeharrison.net/react/
Github codebase:
https://github.com/WebDevLuke/React-Movies
I'm just wondering if anybody can spot the reasoning behind this?
Cheers!
Just a hunch, but you should use a unique key, not just the index of the map function. This way React will understand that the movies are identified not by some iterating index, but an actual value, and that will probably solve your issue.
var movies = this.state.movies.map(function(movie, index){
return (
<Movie key={movie} useKey={index} removeMovieFunction={component.removeMovie} search={movie} toggleError={component.toggleError} />
);
});
This is because React re-evaluates your properties, sees that nothing has changed, and just removes the last <Movie /> from the list. Each Movie's componentDidMount function never runs more than once, and the state of Movie 1, Movie 2 and Movie 3 persists. So even if you supply search={movie} it doesn't do anything, because this.props.search is only used in componentDidMount.
I'm not exactly sure why it isn't rendering correctly as the dataset looks fine.
Looking at the code, I would change your remove function to this...
var index = this.state.movies.indexOf(movieToRemove);
console.log(this.state.movies);
if (index > -1) {
this.state.movies.splice(index, 1);
}
console.log(this.state.movies);
this.setState(this.state.movies);
My assumption is that, the state isn't being updated correctly. Whenever updating state, you should always use setState (unless the convention changed and I wasn't aware).
Also, you shouldn't need to explicitly call forceUpdate. Once setState is called, React will automatically do what it needs to and rerender with the new state.
State should be unidirectional (passed top down) from your top level component (known as a container). In this instance, you have state in your top level component for search strings and then you load individual movie data from within the "Movie" component itself via the IMDB API.
You should refactor your code to handle all state at the top level container and only pass the complete movie data to the dumb "Movie" component. all it should care about is rendering what you pass in it's props and not about getting it's own data.

Resources