A closer look at useEffect — Part 1
In a previous article (link here), I took a deeper dive into React’s useState
hook and promised I would do the same with the useEffect
hook. I’ll break this examination into two parts, since I want to deal with a couple odds and ends that relate to a small widget I built to illustrate useEffect
.
Ok, so, to illustrate this hook, we’re going to build a simple search widget that calls on the Wikipedia API.
We now need to add in some code to take the user’s search term and send a request off to the Wikipedia API. There are actually 2 possible options as to where to write the code to make that request.
In this option, the user types into the text input and we immediately make a request to the API. We do this by adding in some code to the onChange
event handler; inside this event handler, we take the value from the input to make the request, which takes some amount of time. After this short period of time, we then get a response; we extract some data out of that response and we use that to update some piece of state, which we can call results
. The updated piece of state contains the list of search results, our component rerenders and we render out a stylized list of those results.
Sounds great, right? For the most part, yes; unfortunately, there is a slight downside to this option, which becomes clearer after we examine Option #2.
Here, the user types in an input. The onChange
event handler is called and this will update our term
piece of state, causing our component to rerender.
The next step is the key to this option (notice the green text color and the big stars). Inside of our component, we can add some code to detect that our component rerendered and our term
piece of state changed! Once we detect that, we can then make a request to the API…and the remaining steps are the same as in option #1. After a brief period of time, we get results, we can update the results
piece of state, our component rerenders again and we show our results to the user.
The question then becomes: do we make the API request immediately in the onChange
event handler OR do we only want to update some piece of state and make the request only after our component begins to rerender and we detect our term
has changed?
For Option #1, the fact that we can search instantly when the onChange
event triggers seems like a very big plus. However, as I mentioned above, there is a downside to tightly coupling the onChange
event with the search.
For Option #2, the search happens when some piece of state changes. This is VERY beneficial in that we can easily trigger a search when other parameters besides our term
change. For example, let’s say we wanted to add some kind of category selector or filter in to our search form…we could easily do that by selecting option #2. Indeed, with this option, it is much easier to extract code out into a more reusable function!
Wonderful! Fantastic! We’ve answered that question. But what does this have to do with the useEffect
hook?? Well, that add-code-to-detect-term
-has-changed part…that’s what useEffect
allows us to do! The useEffect
hook allows us to write out some code that detects our component is rerendering and some piece of information has changed.
useEffect
We have to keep in mind that we are using a Functional Component here, which does not naturally have access to lifecycle methods. But we will still want to make use of code that runs at particular times, so this is when we can implement the useEffect
hook; this hook allows for Functional Components to take advantage of things that are very similar to lifecycle methods (it’s important to remember that these are not the ACTUAL lifecycle methods).
We can configure useEffect
to run some code automatically in one of three scenarios:
- When the component is rendered for the first time only
- When the component is rendered for the first time and whenever it rerenders
- When the component is rendered for the first time and…the next two things must happen together…whenever it rerenders and some piece of data has changed.
useEffect
takes two arguments. The first argument is always a function. It is the second argument that determines which scenario (1, 2, or 3 above) will be implemented. The three options we can have are:
- an empty array — this leads to scenario 1 from above
- an array with some value (or values) inside of it — this leads to scenario 3 from above
- no argument at all — this leads to scenario 2 from above
Ok, so let’s implement everything we’ve discussed so far.
So, according to our code above, the function we are calling within useEffect
will run when our component renders for the first time AND when it rerenders IF our term
piece of state has changed. This essentially mimics our componentDidMount
lifecycle method, if we were calling setState
within that method.
I wanted to mention a word of caution when using axios
and useEffect
, which I discovered while playing around with this widget. We are not allowed to mark the function we are passing into useEffect
as async
and use any await
keyword directly inside that function!
There are a few solutions to this problem, one of which includes using promises, but React actually provides a suggestion with the error message that it spits out.
React suggests creating a helper function within the function called by useEffect
. That function can be marked as async
and we can use the await
keyword. We then invoke that helper function within useEffect
. We’re going to also include an options object within that call, to which we will give a params
property and assign it an object. Whatever key/value pairs we put inside that object, axios
will code them into a query string and append it on to the end of the url automatically.
Another little aside here. Notice that when I set up my term piece of state, I gave it a default value of ‘dog’.
And in the params object of my axios
call, I included the reasoning why I put a default value to begin with.
Because useEffect
gets called immediately upon the initial render of the component, there will not be a value for the term
piece of state at that point…the user just opened the page and so hasn’t typed anything in yet! If there is no default term
, React will throw an error.
Instead of including a default value for term
, I could have included logic within useEffect
to invoke the search
function ONLY if there is a value for term
. In that case, the end of the useEffect
block of code would look like this:
if (term) {
search();
}
}, [term];
Ok, almost there.
Finally, we need to map over the results
piece of state to build out our list of search results for display (with some very basic Semantic UI styling).
Very last aside for this article. See that dangerouslySetInnerHTML
weirdness? What’s that all about?
Well, the Wikipedia API is a bit quirky and it actually gives us results with HTML in them; the purpose of this, on Wikipedia’s end, is to highlight the search term by using <span class=”searchmatch”></span>
. But it looks very unpleasant for our purposes.
How can we get rid of this? Well, we can write in some logic to search for the presence of a <span>
and remove it. However, there is a ‘hidden’ feature in React that does the trick as well, the dangerouslySetInnerHTML
! You provide it with an object whose key MUST be double-underscore html
(__html
) and the value set to whatever you are trying to render. I wanted to include it here because it was something surprising that I discovered! That being said, I also discovered that it’s NOT
something you should use frequently; and certainly NOT if you do not trust the site you are querying. Why? Well, anytime you take a string from a third party, you could be introducing a security hole into your app…in particular, one called an XSS attack, which stands for a cross site scripting attack. This is where we accidentally pick up and render some html
from an untrusted source, and this could allow someone to execute some javascript inside of our app. So use that dangerouslySetInnerHTML
carefully, if at all!
At this point, then, we have basic search functionality with our useEffect
hook.
But if we actually want to see each entry in its entirety, we’ll have to do a bit more. And that will be the subject of my next article!