If you're reading this book, you probably know what React is. If not, don't worry. I'll do my best to keep philosophical definitions to a minimum. However, this is a long book with a lot of content, so I feel that setting the tone is an appropriate first step. Yes, the goal is to learn React and React Native. But it's also to put together a lasting architecture that can handle everything we want to build with React today and in the future.
This chapter starts with a brief explanation of why React exists. Then, we'll think about the simplicity of React and how React is able to handle many of the typical performance issues faced by web developers. Next, we'll go over the declarative philosophy of React and the level of abstraction that React programmers can expect to work with. Finally, we'll touch on some of the major features of React.
Once you have a conceptual understanding of React and how it solves problems with UI development, you'll be better equipped to tackle the remainder of the book.
This chapter will cover the following topics:
- What is React?
- React Features
- What's new in React?
What is React?
I think the one-line description of React on its home page (https://facebook.github.io/react) is concise and accurate:
It's a library for building user interfaces (UIs). This is perfect because, as it turns out, this is all we want most of the time. I think the best part about this description is everything that it leaves out. It's not a mega framework. It's not a full-stack solution that's going to handle everything from the database to real-time updates over WebSocket connections. We might not actually want most of these prepackaged solutions.
If React isn't a framework, then what is it exactly?
React is just the view layer
React is generally thought of as the view layer in an application. You might have used a library such as Handlebars or jQuery in the past. Just like jQuery manipulates UI elements and Handlebars templates are inserted into the page, React components change what the user sees. The following diagram illustrates where React fits in our frontend code:
This is all there is to React—the core concept. Of course, there will be subtle variations to this theme as we make our way through the book, but the flow is more or less the same. We have some application logic that generates some Data. We want to render this Data to the UI, so we pass it to a React Component, which handles the job of getting the HTML into the page.
You may wonder what the big deal is; React appears to be yet another rendering technology. We'll touch on some of the key areas where React can simplify application development in the remaining sections of the chapter.
Simplicity is good
React doesn't have many moving parts to learn about and understand. Internally, there's a lot going on, and we'll touch on these things throughout the book. The advantage of having a small API to work with is that you can spend more time familiarizing yourself with it, experimenting with it, and so on. The opposite is true of large frameworks, where all of your time is devoted to figuring out how everything works. The following diagram gives you a rough idea of the APIs that we have to think about when programming with React:
React is divided into two major APIs:
- The React Component API: These are the parts of the page that are actually rendered by React DOM.
- React DOM: This is the API that's used to perform the actual rendering on a web page.
Within a React component, we have the following areas to think about:
- Data: This is data that comes from somewhere (the component doesn't care where), and is rendered by the component.
- Lifecycle: This consists of methods or Hooks that we implement to respond to the component's entering and exiting phases of the React rendering process as they happen over time. For example, one phase of the lifecycle is when the component is about to be rendered.
- Events: These are the code that we write for responding to user interactions.
- JSX: This is the syntax of React components used to describe UI structures.
Don't fixate on what these different areas of the React API represent just yet. The takeaway here is that React, by nature, is simple. Just look at how little there is to figure out! This means that we don't have to spend a ton of time going through API details here. Instead, once you pick up on the basics, we can spend more time on nuanced React usage patterns that fit in nicely with declarative UI structures.
Declarative UI structures
For example, think about using something like jQuery to build your application. You have a page with some content on it, and you want to add a class to a paragraph when a button is clicked. Performing these steps is easy enough. This is called imperative programming, and it's problematic for UI development. While this example of changing the class of an element is simple, real applications tend to involve more than three or four steps to make something happen.
React components don't require executing steps in an imperative way. This is why JSX is central to React components. The XML-style syntax makes it easy to describe what the UI should look like. That is, what are the HTML elements that this component is going to render? This is called declarative programming and is very well suited for UI development. Once you've declared your UI structure, you need to specify how it changes over time.
Time and data
Another area that's difficult for React newcomers to grasp is the idea that JSX is like a static string, representing a chunk of rendered output. This is where time and data come into play. React components rely on data being passed into them. This data represents the dynamic parts of the UI. For example, a UI element that's rendered based on a Boolean value could change the next time the component is rendered. Here's a diagram of the idea:
Each time the React component is rendered, it's like taking a snapshot of the JSX at that exact moment in time. As your application moves forward through time, you have an ordered collection of rendered UI components. In addition to declaratively describing what a UI should be, re-rendering the same JSX content makes things much easier for developers. The challenge is making sure that React can handle the performance demands of this approach.
Using React to build UIs means that we can declare the structure of the UI with JSX. This is less error-prone than the imperative approach of assembling the UI piece by piece. However, the declarative approach does present a challenge: performance.
For example, having a declarative UI structure is fine for the initial rendering, because there's nothing on the page yet. So, the React renderer can look at the structure declared in JSX and render it in the DOM browser.
This concept is illustrated in the following diagram:
On the initial render, React components and their JSX are no different from other template libraries. For instance, Handlebars will render a template to HTML markup as a string, which is then inserted into the browser DOM. Where React is different from libraries such as Handlebars is when data changes and we need to re-render the component. Handlebars will just rebuild the entire HTML string, the same way it did on the initial render. Since this is problematic for performance, we often end up implementing imperative workarounds that manually update tiny bits of the DOM. We end up with a tangled mess of declarative templates and imperative code to handle the dynamic aspects of the UI.
We don't do this in React. This is what sets React apart from other view libraries. Components are declarative for the initial render, and they stay this way even as they're re-rendered. It's what React does under the hood that makes re-rendering declarative UI structures possible.
React has something called the virtual DOM, which is used to keep a representation of the real DOM elements in memory. It does this so that each time we re-render a component, it can compare the new content to the content that's already displayed on the page. Based on the difference, the virtual DOM can execute the imperative steps necessary to make the changes. So, not only do we get to keep our declarative code when we need to update the UI, but React will also make sure that it's done in a performant way. Here's what this process looks like:
With performance concerns addressed, we need to make sure that we're confident that React is flexible enough to adapt to different platforms that we might want to deploy our apps to in the future.
The right level of abstraction
Another topic I want to cover at a high level before we dive into React code is abstraction.
In the preceding section, you saw how JSX syntax translates to low-level operations that update our UI. A better way to look at how React translates our declarative UI components is via the fact that we don't necessarily care what the render target is. The render target happens to be the browser DOM with React, but it isn't restricted to the browser DOM.
React has the potential to be used for any UI we want to create, on any conceivable device. We're only just starting to see this with React Native, but the possibilities are endless. I personally will not be surprised when React Toast becomes a thing, targeting toasters that can singe the rendered output of JSX onto bread. The abstraction level with React is at the right level, and it's in the right place.
The following diagram gives you an idea of how React can target more than just the browser:
From left to right, we have React Web (just plain React), React Native, React Desktop, and React Toast. As you can see, to target something new, the same pattern applies:
- Implement components specific to the target.
- Implement a React renderer that can perform the platform-specific operations under the hood.
This is, obviously, an oversimplification of what's actually implemented for any given React environment. But the details aren't so important to us. What's important is that we can use our React knowledge to focus on describing the structure of our UI on any platform.
Now that you understand the role of abstractions in React, let's see what's new in React 16.
The second edition of this book covers the major changes in React 16. I'm leaving this section intact for the third edition because I think the changes that were introduced in React 16 are still new and important enough to be relevant to learning React.
The features of React 16 include the following:
- Revamped core architecture
- Lifecycle methods
- Context API
- Rendering fragments
- Rendering lists and strings
- Handling errors
- Server-side rendering
Let's look at each new feature in detail.
Revamped core architecture
Perhaps the biggest change in React 16 is the change made to the internal reconciliation code. These changes don't impact the way that you interact with the React API. Instead, these changes were made to address some pain points that were preventing React from scaling up in certain situations. For example, one of the main concepts of this new architecture is that of fibers. Instead of rendering every component on the page in a run-to-compilation way, React renders fibers—smaller chunks of the page that can be prioritized and rendered asynchronously.
For a more in-depth look at this new architecture, these resources should be helpful:
React 16 had to revamp some of the lifecycle methods that are available to class components. Some lifecycle methods are deprecated and will eventually be removed because they will be problematic for future async rendering functionality in React. For example, a common way to initialize state in a React component is to use the componentWillMount() lifecycle method. Once this method is removed from React, you can just set the initial state directly as an instance value.
For more information on these lifecycle methods, visit https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.
The Context API
React has always provided a Context API for developers, but it was always considered experimental. Context is an alternative approach to passing data from one component to the next. For example, using properties, you can passing data through a tree of components that is several layers deep. The components in the middle of this tree don't actually use any of these properties—they're just acting as intermediaries. This becomes problematic as your application grows because you have lots of properties in your source that add to the complexity.
The new Context API in React 16.3 is more stable than previous versions and provides a way for you to supply your components with data at any tree level. You can read more about the new Context API here: https://reactjs.org/docs/context.html.
If your React component renders several sibling elements, say three <p> elements, for instance, you would have to wrap them in <div> because React would only allow components to return a single element. The only problem with this approach is that it leads to a lot of unnecessary DOM structure. Wrapping your elements with <Fragment> is the same as wrapping them with <div>, except there won't be any superfluous DOM elements.
You can read more about fragments here: https://reactjs.org/docs/fragments.html.
When a React component returns content, it gets rendered into its parent component. Then, that parent's content gets rendered into its parent component and so on, all the way to the tree root. There are times when you want to render something that specifically targets a DOM element. For example, a component that should be rendered as a dialog probably doesn't need to be mounted at the parent. Using a portal, you can control precisely where your component's content is rendered.
You can read more about portals here: https://reactjs.org/docs/portals.html.
Rendering lists and strings
Prior to React 16, components had to return either an HTML element or another React component as its content. This can restrict how you compose your application. For example, you might have a component that is responsible for generating an error message. You used to have to wrap strings in HTML tags or map list items to HTML tags in order to be considered a valid React component output. Now you can just return the string. Similarly, you can just return a list of strings or a list of elements.
This blog post introducing React 16 has more details on this new functionality: https://reactjs.org/blog/2017/09/26/react-v16.0.html.
Having error boundaries in place like this allows you to structure your components in a way that best suits your application. You can read more about error boundaries here: https://reactjs.org/docs/error-boundaries.html.
Server-side rendering (SSR) in React can be difficult to wrap your head around. You're rendering on the server, then rendering on the client too? Since the SSR pattern has become more prevalent, the React team has made it easier to work within React 16. In addition, there are a number of internal performance gains as well as efficiency gains by enabling streaming rendered content to the client.
If you want to read more about SSR in React 16, I recommend the following resources:
However, in this book, the focus will be on using Next.js for SSR since it's so much easier than using a manual setup. Next.js is a simple framework for building React applications that handles many gory details related to routing and SSR.
Now that you're familiar with the big changes that came with React 16, it's time to take a look at the cutting edge features available in the latest React release.
What's new in React?
The third edition of this book includes React features that were introduced after version 16.6.0. In the following sections, I'll give you a brief introduction to the new functionality. Each feature will be covered in greater detail as you make your way through the book.
For now, we will briefly look at the following:
- Memoizing functional components
- Cook splitting and loading
Let's start exploring them.
Memoizing functional components
The React.memo() function is the modern equivalent of the PureComponent class. Memoized components avoid re-rendering if the component data hasn't changed. In the past, you would extend your class component with PureComponent. This would automatically handle checking whether the component data has changed or not and whether or not the component should re-render.
The challenge with this approach is that it is now common for large React applications to have a lot of functional components. Before React.memo(), there was no way to memorize components so that they could avoid re-rendering if no data changes happened. Now, you can pass your functional components to React.memo() and they'll behave like PureComponent.
You can read more about React.memo() here: https://reactjs.org/docs/react-api.html#reactmemo.
Code splitting and loading
Prior to the React.lazy() function, code splitting in large React applications was cumbersome. Code splitting is important for large applications because it reduces the size of the code bundles that are sent to the browser, which can dramatically improve the user experience. Some features of an application might never be used, which means that the code that implements those features is never delivered to the browser. This is a huge efficiency gain.
With the addition of React.lazy(), React acknowledges that code splitting and the user experience of waiting for pieces of the application to load are integral parts of the application, not an afterthought. By combining React.lazy() and the Suspense component, we get fine-grained control over how our app is split up and what happens while the user waits for it to load.
You can read more about code splitting here: https://reactjs.org/docs/code-splitting.html.
One of the most consequential new features of React is Hooks—functions that extend the behavior of functional React components. Hooks are used to "hook into" the React component machinery from your React components. Instead of relying on classes to build components that have state or that rely on executing side effects when the component is mounted, you can use the React Hooks API to pass functions that handle these cases.
The end result is having more flexibility with how you're able to compose React components since functions are more easily shared between modules than component class methods are. Hooks are the future of how React components are assembled, which will have a big impact on the third edition of this book, where there's a new chapter devoted to Hooks, as well as updated code in all chapters from the second edition.
You can read more about Hooks here: https://reactjs.org/docs/Hooks-intro.html.
In this chapter, you were introduced to React at a high level. React is a library, with a small API, used to build UIs. Next, you were introduced to some of the key concepts of React. We discussed the fact that React is simple because it doesn't have a lot of moving parts. Next, we looked at the declarative nature of React components and JSX. Then, you learned that React takes performance seriously and that this is how we're able to write declarative code that can be re-rendered over and over. Next, you learned about the idea of render targets and how React can easily become the UI tool of choice for all of them. Lastly, I gave you a rough overview of what's new in React 16.x.
That's enough introductory and conceptual stuff for now. As we make our way toward the end of the book, we'll revisit these ideas. For now, let's take a step back and nail down the basics, starting with JSX.
Take a look at the following links for more information:
- React: https://facebook.github.io/react
- Introducing Hooks: https://reactjs.org/docs/hooks-intro.html
- React Fiber Architecture: https://github.com/acdlite/react-fiber-architecture
- React v16.0: https://reactjs.org/blog/2017/09/26/react-v16.0.html
- Update on Async Rendering: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
- Context: https://reactjs.org/docs/context.html
- Fragments: https://reactjs.org/docs/fragments.html
- Portals: https://reactjs.org/docs/portals.html
- Error Boundaries: https://reactjs.org/docs/error-boundaries.html
- What’s New With Server-Side Rendering in React 16: https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67
- ReactDOMServer: https://reactjs.org/docs/react-dom-server.html