Single Page Applications and Functional Components
Stateless Functional Components
You may have noticed that we're only able to view our landing page when serving content with parcel. That's because parcel assumes that all of your content will be rendered dynamically with JavaScript... it isn't built to handle multiple HTML files.
This kind of application is called a Single-Page Application, or SPA. The idea is to listen for user interaction and respond quickly, without needing to make another network request for a set of assets. Let's see if we can get this to work with our current portfolio project! But before we do, there's a bit more that we need to learn about callbacks and the event Object.
Callbacks
With events, we've come across our very first callback functions. This is a common pattern in JavaScript based on the idea that functions are first-class citizens; functions can be returned from other functions, can be invoked at any time, and can be passed in as arguments to other functions. When a function is given to another function as an argument for later invocation, that function argument is called a callback. Here's a simple example:
function callWithFive(callback) {
return callback(5);
}We've now created a function that calls its argument with 5, come hell or high water. That callback could be any function... maybe it's console.log, maybe it's a multiplyByThree function that multiplies its argument by 3, maybe it's a function that doesn't do anything at all. The only thing we can be certain of is that the callback function will always be called with 5 as its first (and only) argument.
This pattern might eventually help us out with rendering components! Think about how this might work:
function invokeWithState(state, Component) {
return Component(state);
}Now we've created a wrapper that passes a state Object to any functional Component. We could implement this in our portfolio right now, although we're going to hold off for a minute. Instead, think about how this might relate to the addEventListener function. As you might recall, the second argument to addEventListener is a callback function that's executed whenever the first argument (e.g. click) occurs. If you understand that, you might be wondering: are there any arguments passed to the event handler/callback when the associated event is triggered? The answer is yes, and that argument is an Object called the event Object.
event Object
event ObjectTry the following in your developer console:
You should now see a very large Object logged to the console whenever you click on a page. Let's take a look at two of the most important properties of this Object.
event.preventDefault
event.preventDefaultIn a previous exercise, you were tasked with adding event listeners to some of the links in your Navigation component. You might have done so like this:
You may have noticed, however, that the default link behavior took over in this case, and that the phrase the first link was clicked! was never output to the console. How could we prevent that default anchor tag action of reloading the page? Try something like this:
preventDefault is a function that can be invoked to make sure that the built-in event behavior of a particular HTML element doesn't occur. This allows us to change the way that things like anchor tags and forms behave in the context of a Single-Page Application. Try it out in your portfolio project!
Portfolio Project 1
SPA navigation
Up to this point, we haven't been able to see any of our other pages. Let's see if we can render the contents of these other by mimicking the routing behavior of the browser!
Let's start by wrapping all of our rendering logic into a single function:
If everything from part 1 went as planned, you should see no difference on your landing page. Let's start by coming up with a new state for your first link. Perhaps:
Now where should put our event listeners? We can't put them before
render, since we can't apply listeners to elements before they've been rendered. And we also need to re-apply listeners after each render. That means that we should include the event listeners inside ofrender, but afterinnerHTMLhas been overwritten, a la:Let's repeat the process for your
contactandprojectslinks! As you work through this problem set, can you think of some limitations with this approach? Before we progress much further, we need to learn aboutevent.target.
event.target
event.targetThe SPA navigation implementation above is certainly working, but it doesn't scale very well, and certainly isnt very DRY (i.e. it fails the first law of programming: Don't Repeat Yourself). To make this more abstract, though, we need to be able to get some information out of the anchor tag that was the source of the click event in the first place. We can extract this information with event.target. Try the following in your dev tools:
You should find that the DOM representation of anchor tag is logged out to the console! This is the exact same JavaScript Object that's returned from something like document.querySelector in the first place, so you can get all sorts of information out of event.target. Try the following:
Let's use this new tool to improve our SPA navigation.
Portfolio Project 2
Better SPA Navigation
At the end of the last exercise, your render function probably looked something like this:
That's neither DRY nor pretty... just look as those nasty selectors that we need to select each anchor tag in turn. Let's see if we can clean this up using the power of event.target.
First, let's refactor our states. You probably have four separate state Objects by this point. If we combine them into one flat state Object, then it will be easier to leverage the power of
event.target. Let's do something like this:Now we can extract the click event handler into its own function:
This already helps us clean things up. Check out our new
renderfunction:Our last optimization will be to reduce the number of DOM queries every re-render. We're currently querying the entire
documentthree times every timerenderis called. We can do better by usingquerySelectorAll, like so:
Now we have a pretty clean navigation system that doesn't require page refreshes! How else can we improve on this system?
Last updated