- The History of the Web
- The React Way
- Passing Props
- Managing State
- Rendering
- Managing Effects
- Non-visual Values
- Teleporting Data
- Concurrent Rendering
- Server Components

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.
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.
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.
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).
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.
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.
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.
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.
If you're enjoying React Visualized, you'll love react.gg – our brand new, interactive way to master modern React.
And for the next little bit you can get 30% off during our launch sale.
Learn MorePassing 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.
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.
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 components 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. Oftentimes 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.
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.
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.
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.
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.
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.
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.
Come on what are you waiting for give us your money it'll feel good I promise – react.gg
And for the next little bit you can get 30% off during our launch sale.
Learn MoreManaging 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.
The first thing to understand when it comes to managing effects in React is that there’s a set of rules you need to follow to ensure that your components behave predictably and efficiently.
These rules help you manage side effects in a way that aligns with React’s rendering model.
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
useEffect is a hook that comes with React that, as our definition suggests, allows you to run a side effect that synchronizes your component with some outside system. useEffect works by removing the side effect from React’s rendering flow and delaying its execution until after the component has rendered.
Conceptually this makes sense, especially when combined with Rule #1. React can maximize the speed and predictability of rendering by enforcing rules around when side effects can run – either when an event occurs (Rule #1) or after the component has rendered (Rule #2).
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.
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.
or
That’s literally all it does – it’s the Wormhole, Einstein-Rosen bridge, TARDIS, Stargate, Portkey, DeLorean of React.
Please buy our course I beg of you, react.gg
Learn MoreConcurrent Rendering
Though it's not often talked about, Concurrent Rendering is the foundation for which all the latest React features like actions and transitions are built on.
Historically, if React was rendering and a high-priority event like a user input occurred, because all of React’s work happened on the main thread, React would have no choice but to finish rendering before it could process that event.
What the React team realized was that if they could enhance React to distinguish between low-priority tasks, like rendering a list, and high-priority tasks, like handling user input or animations, while also allowing React to seamlessly switch between those tasks, then the performance of every React app may improve since React would be able to prioritize the most important work first.
So this is exactly what they did and they released it as part of React v18 along with a set of new APIs for consuming these concurrent features.
First was useDeferredValue.
You can think of useDeferredValue as a way to tell React to defer updating a value until all its high-priority rendering work has finished.
Why is that helpful? Because it allows React to keep showing an existing, computationally heavy but lower-priority portion of the UI while it prioritizes updating a higher-priority section of the UI.
Now what if instead of using useDeferredValue to defer updating part of the UI, you wanted to be a little more explicit and just tell React which state updates may lead to computationally expensive work?
This is exactly what startTransition is for.
Whenever you wrap a state update inside of React.startTransition, that tells React that that update, which we’ll call a transition, may trigger a computationally expensive render, and as such, to not block any other higher-priority events that may occur during that render.
The way it works is while React is busy working on the transition, it will continue to show the user what they were already seeing, while at the same time, checking every 5ms to see if there are any other higher-priority events that it should prioritize.
If there are, it will pause the work it’s doing and shift its priority.
If not, or once all the high-priority work has finished, it’ll go back to working on the transition, committing the work all at once when it’s finished – updating the UI.
And if you needed to know the current state of the transition, you could use React’s useTransition hook.
When you invoke useTransition, you’ll get back an array with two values – isPending and startTransition.
const [isPending, startTransition] = React.useTransition()
Yes, that startTransition is the same as the React.startTransition we just saw, but now it’s coupled to an isPending value which will be true until the transition is complete.
React 18 was a fundamental change to React as it introduced the concept of transitions, giving React the ability to interrupt rendering and prioritize higher-priority updates over lower-priority ones.
There was just one problem: they weren’t super useful.
In React v18, transitions were all about CPU work. But unless you were building a computationally expensive app for low-end devices, odds are the CPU wasn’t the bottleneck.
But in almost every web app, you know what is? The network.
What the React team realized was that if they could extend transitions to work across the network, then, not only would they become much more useful, but they would also solve React’s biggest weakness up until that point – mutations.
They already had a simple API for handling this sort of lifecycle with transitions, all they needed to do was extend them to work across the network.
So in React v19, that’s exactly what they did by giving transitions the ability to support async functions – and they even gave them a fun name, “actions”.
An added benefit of using actions for mutations is that React is able to batch all the transitions together, only updating the final UI once all the transitions are complete.
This prevents any UI flickering when each state update resolves independently of one another.
You can see this in action if you comment out the transitions in the code above and then quickly change the quantity.
Server components
Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server.
In a way, you can think of Server Components as fancy, serializable, HTML template generators. Their entire job is to compute the UI and ship it to the client.
With Server Components, all that’s shipped to the user is the end result of the computation.
And more important than the bundle size benefits are the compositional benefits.
With Server Components, because they respect any Suspense or ErrorBoundary you put in place, the same traditional mental model you’ve always had for React can now extend across the network.
In this code, Layout, Sidebar, Content, and Footer are all Server Components, with Content being wrapped in a Suspense boundary since it needs to do some work.
This enables an experience where the user is able to see something as quickly as possible, with the dynamic parts being streamed in when they become available.
But what would it look like if the requirements changed? For example, what if Content needed to show a Carousel of images rather than just one?
In this scenario it could no longer be a Server Component because it would need some JavaScript (React) to run. This also means we can no longer use async/await in the component without breaking the rules of React.
So what do we do?
We could obviously make a new API endpoint and do the fetching on the client with useEffect or React.use, but ideally we want to start fetching sooner rather than later.
A more “React like” solution would be to move the request up to the parent Server Component, initiate it, and then pass the promise down to the Content component to unwrap with React.use.
Pretty cool. We’re passing a prop from the server to the client and it Just Works™.
And there’s another approach that you could argue is even better.
Again, Server Components are just fancy, serializable, HTML template generators. If you look back at the Content component, there is some template logic in there.
If we move that out of the Client component and into the Server Component, we can create the template there and pass it as a children to the Client component to render.
This is the best of both worlds and it’s my favorite pattern with Server Components – I call it the “slot” pattern.
You use the server to create as much of the template as you can, and then you sprinkle in any interactivity via use client as necessary.
Composition, over the network.
If you enjoyed this, you'll love react.gg – our brand new, interactive way to master modern React.
And for the next little bit, you can get 30% off during our launch sale.
Learn More