A gentle introduction to React, but visualized.

TOP

The History of the Web

In order to truly appreciate React, you first have to understand the historical context for why React was created. From jQuery, to Backbone, to AngularJS – each era inspired React in different ways.

jQuery was the most popular way to build for the web. It embraced websites for what they truly were, a tree of DOM nodes.

Interactive diagram that shows traversing DOM nodes, finding the right one, and changing itVIEWDOM

With jQuery, the state of your application lived inside the DOM. Whenever you wanted to update that state, you’d imperatively traverse the DOM, find the node you wanted to update, then update it. Needed to respond to an event? Again, traverse the DOM, find the node, then add an event listener to it.

Backbone.js invented the Model-View-Controller pattern. That’s a joke, but it was the first popular JavaScript framework that embraced the traditional software design pattern and brought it to building for the web.

Interactive diagram showing data moving from models into viewsMODELSVIEWS

In just 2,000 lines of code, Backbone allowed you to decouple your application’s state from the DOM. Instead of living in the DOM, Backbone state lived inside of its “Models”. From there, whenever a Model changed, all the Views that cared about that Model’s state would re-render.

AngularJS, for good and for bad, embraced two-way data binding. It was Angular’s way of updating the view whenever the model changed, and updating the model, whenever the view changed.

Interactive diagram showing data moving back and forth between views and modelsMODELSVIEW

In theory, this was nice because you didn’t have to worry about doing manual DOM manipulation yourself. In practice, well, implicit state changes usually led to code that is both hard to follow and hard to debug. It also led to performance issues since Angular.js had to constantly scan your app looking for state changes.

The React Way

The problem with most UI libraries and frameworks prior to React was that they did a poor job of handling mutations. This naturally led to apps that were both hard to debug and hard to follow. But what if you could avoid this problem entirely? Now obviously you can’t completely avoid mutations, but could you create an abstraction which minimized its negative effects?

This is at the heart of what React tries to do.

One of the core innovations of React was that they made the View a function of your application’s State. Often represented as v = f(s).

Diagram showing that view = function(state)tateunctioniewv=fs)(

All you have to do is worry about how the state in your application changes, and React handles the rest.

But making your view a function of your state was really only half the picture. The real innovation happened when you encapsulated this idea into a proper component based API.

Interactive diagram showing that a view is a function of state: when state changes the view changes) )= f ( g ( VIEWs

By doing so, the same intuition you have about creating and composing together functions can directly apply to creating and composing components. However, instead of composing functions together to get some value, you can compose components together to get some UI.

To truly embrace a component based API, React needed a way to allow you to describe what the UI for a component would look like from inside the component itself. This is where most of the initial hate for React came from and it had to do with React’s interpretation of the “Separation of Concerns” principle.

HTML, CSS, and JS are separate pillarsHTMLCSSJS

Historically the way you adhered to this on the web was to have your HTML separate from your CSS which was separate from your JavaScript.

In React’s opinion, anything that had to do with rendering the View - whether that be state, UI, and in some cases, even styling, was part of its concern.

Interactive diagram that toggles between a traditional approach of separation of concerns (HTML, CSS, and JS) and the React approach (all three combined in a component)COMPONENTSHTMLCSSJS

This approach allows you to collocate concerns, regardless of technologies.

To accomplish this, React needed a way to allow you to describe what the UI for a component would look like from inside the component itself. Well, describing UI is already a solved problem - that’s what HTML is really good at. But you can’t mix HTML and JavaScript, right?

Why not? This thinking led to the creation of JSX - a wonderful lovechild of HTML and JavaScript that allows you to write HTML-ish looking syntax directly inside of JavaScript.

react.gg is the all new interactive way to master modern React.

For course updates, exclusive discounts, previews, and some fun surprises, drop your email below.

Get the Course

Passing Props

Whenever you have a system that is reliant upon composition, it’s critical that each piece of that system has an interface for accepting data from outside of itself. React accomplishes this via props.

Props are to components what arguments are to functions.

The same intuition you have about functions and passing arguments to functions can be directly applied to components and passing props to components.

Along with passing data to components as attributes, you can also pass data between the opening and closing brackets of an element. When you do this, that data is accessible in the component via props.children.

You can think of a component with a children prop as having a placeholder that can be filled by its parent component.

{props.children}VIEWfunctionLayout (props) {return (<div className="layout"></div><SideBar /><Footer />{props.children}})<Layout></Layout><Pizza /><Taco />

Because of this, it’s common to utilize children to create Layout type components that encapsulate styling and logic, but leave the content to the consumer of the component.

Managing State

State, and specifically the ability for individual components to own and manage their own state, is what makes React so powerful, and it’s what allows you to build complex user interfaces out of simple, isolated components.

Because React components are just functions, normal variables won’t persist across different invocations of those functions (which React calls renders). To add state that persists across different renders, you can use React’s useState hook.

Whenever React sees that the state of a component has changed, it will trigger a re-render and update the UI.

COMPONENTupdate()VIEW

The biggest tradeoff with having individual components manage their own state is often you’ll have state that multiple components need to access. Learning to handle these scenarios is one of the most important aspects of properly managing state in React.

Here’s the rule of thumb – whenever you have state that multiple component depend on, you’ll want to lift that state up to the nearest parent component and then pass it down via props.

The only gotcha is how you update that state. Often time when lifting state up, you decouple where the state lives from the event handlers that update that state.

To solve this, you’ll create an updater function in the parent component where the state lives and, via props, invoke that function from the child component(s) where the event handler(s) live.

()update()update()

Here’s what this would look like in a simple Todo app. Instead of having each individual Todo component manage its own state, we’d lift that state up to a parent TodoList component, then pass down a function which gives each individual Todo component the ability to update itself.

<TodoList />1<Todo />112<Todo />223<Todo />33

But all this talk of state brings up a good question, how exactly does React update its state?

Rendering

Rendering is just a fancy way of saying that React calls your function component with the intent of eventually updating the view. Let’s look at what happens during this process.

When React renders a component, two things happen.

First, React creates a snapshot of your component which captures everything React needs to update the view at that particular moment in time. Props, state, event handlers, and a description of the UI (based on those props and state) are all captured in this snapshot.

COMPONENTVIEWSNAPSHOT

From there, React takes that description of the UI and uses it to update the View.

Now that you know how React renders, the natural next question is when does React render? The answer is surprisingly simple. React will only re-render when the state of a component changes.

Here’s how it works. When an event handler is invoked, that event handler has access to the props and state as they were in the moment in time when the snapshot was created.

handleToggleCOMPONENTVIEWSNAPSHOT

From there, if the event handler contains an invocation of useState’s updater function and React sees that the new state is different than the state in the snapshot, React will trigger a re-render of the component – creating a new snapshot and updating the view.

We can see this process in action with this very simple application.

Whenever our button is clicked, our handleClick event handler will run. The state (index) inside of handleClick will be the same as the state in the most recent snapshot. From there, React sees there’s a call to setIndex and that the value passed to it is different than the state in the snapshot – triggering a re-render.

That’s a lot of words. Here’s what it would look like if we visualized it.

handleClickCOMPONENTVIEWSNAPSHOT012HELLOHOLABONJOUR, TYLERNEXT GREETING

One aspect of rendering that may not be quite so intuitive is that whenever state changes, React will re-render the component that owns that state and all of its child components – regardless of whether or not those child components accept any props.

change()update()update

I get this may seem like a strange default. Shouldn’t React only re-render child components if their props change? Anything else seems like a waste.

First, React is very good at rendering.

Second, the assumption that React should only re-render child components if their props change works in a world where React components are always pure functions and props are the only thing these components need to render. The problem, as anyone who has built a real world React app knows, is that isn’t always the case.

And third, if you do have an expensive component and you want that component to opt out of this default behavior and only re-render when its props change, you can use React’s React.memo higher-order component.

react.gg is the all new interactive way to master modern React.

For course updates, exclusive discounts, previews, and some fun surprises, drop your email below.

Get the Course

Managing Effects

When React renders, it does so with the goal of eventually updating the UI. This entire process needs to be as fast as possible. To keep it fast, React should be able to render without running into any side effects.

If that’s the case, where do we put side effects then? Let’s take a look.

We’re still working on the visuals for this section, but here’s a set of rules we follow for managing side effects in React.

Rule #0

When a component renders, it should do so without running into any side effects

Rule #1

If a side effect is triggered by an event, put that side effect in an event handler

Rule #2

If a side effect is synchronizing your component with some outside system, put that side effect inside useEffect

Rule #3

If a side effect is synchronizing your component with some outside system and that side effect needs to run *before* the browser paints the screen, put that side effect inside useLayoutEffect

Rule #4

If a side effect is subscribing to an external store, use the useSyncExternalStore hook

Non-Visual Values

State allows you to preserve a value across renders and trigger a re-render when it changes.

But sometimes you need a way to tell React that you want to preserve a value across renders, but that value has nothing to do with the view, and therefore, React doesn’t need to re-render when it changes. That’s what a ref is for.

useRef creates a value that is preserved across renders, but won’t trigger a re-render when it changes.

COMPONENTupdate()VIEWref.current

Refs are handy for keeping track of non-visual state like timer ids or keeping track of DOM nodes.

Teleporting Data

Whenever you have an app that is a collection of components, there’s a linear relationship between the size of your application and how difficult it is to share state across that application.

Take this scenario, for example. What you’ve been taught is that whenever you have state that multiple component depend on, you’ll want to lift that state up to the nearest parent component and then pass it down via props.

This works… most of the time. But what happens if instead of your app looking like that, it looked like this?

It’s rare, but there are times when passing props through intermediate components can become overly redundant at best, or completely unmanageable at worst.

Because this is such an obvious limitation of a component based architecture, React comes with a built-in API to solve it called Context. You can think of Context as giving you the ability to teleport data anywhere in your component tree, without needing to pass props.

That’s literally all it does – it’s the Wormhole, Einstein-Rosen bridge, TARDIS, Stargate, Portkey, DeLorean of React.

More to come

There are still a few more sections we need to flush out, but we hope you enjoyed this regardless.

If you did and want to check out more, take a peek at react.gg. It’s our take at a fun, interactive way to master modern React.

react.gg is the all new interactive way to master modern React.

For course updates, exclusive discounts, previews, and some fun surprises, drop your email below.

Get the Course