Skip to content

dr-bizz/intro-to-react-2025

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Intro to React

A practical, opinionated tour of how React works in 2025 — from why it exists, through JSX and components, into rendering, hooks, and the rules they follow. Each section ends with a runnable CodePen so you can poke at the ideas yourself.

Table of contents


History of React

jQuery, Backbone, and Angular were the main ways to build UIs before React (2013).

jQuery

jQuery would traverse the DOM, find the node you wanted, and manipulate the DOM. It relied on shared mutable state, which turned into a mess of mutations and was very hard to keep track of.

jQuery needed structure.

AngularJS

AngularJS is an all-in-one MVC framework for building web applications. Angular loves two-way binding, but that does mean it's constantly looking for state changes, which can cause performance issues. Angular isn't bad, but the world moved on to React.

React

React puts all the visual code for a particular piece of UI, plus its state and logic, into one file called a component.

You compose components together to build a UI.

React needed a way to describe the UI from within the component. This thinking led to the creation of JSX — a wonderful love child of HTML and JavaScript.

React avoids mutation. When it renders, it diffs the previous view against the new one and applies the minimum DOM changes.

  • No data binding
  • No model checking
  • All the code for nested components can be simplified and hidden behind the React component API

React vs older frameworks Component composition

As React fused the UI to the component, it became difficult to express non-visual logic. That's why React hooks were created:

  • useState
  • useEffect
  • useRef
  • useContext
  • useReducer
  • useMemo
  • useCallback

Pure functions in React

What is a pure function?

  • It has no side effects
  • The return value is determined only by its inputs
  • The return value is always the same for the same inputs

Why pure?

React components are pure functions, so they need to be predictable.

React components don't depend on the context of the application — instead they receive inputs via explicit parameters. This makes them extremely composable and reusable, and means their output can be cached because we know what it will be for a given input.

What makes a function impure?

  • Side effects
  • Relying on external state
  • Inconsistent outputs

Guess if this is a pure or impure function:

Pure or impure?

It relies on TAX_RATE, which is external state — so it's impure.

What are side effects?

Anytime a function does anything other than what a pure function does — whether by relying on state outside its inputs, or by creating an observable change to the program — it's said to have a side effect.

  • Async requests to fetch data to render
  • External state

🔗 CodePen example

Side effects = unpredictable. Inconsistent outputs = unpredictable.

useEffect lets us call a request after the component has first been rendered. Same with useState: we set the initial state, and only after the component has first rendered can we update the state.

JSX

It's fully JS, with syntax that looks like HTML.

Components do not return HTML. They return JS that React turns into a description of UI.

Rules of JSX

  • Only return one parent element from a component
  • class becomes className
  • No dashes in attributes — convert to camelCase
  • style={{ marginBottom: '25px' }}
  • Wrap expressions in {donations && …}
  • Return null if you don't want to render anything
  • Use conditional rendering
  • React relies heavily on JS out of the box
  • You can loop over an array with .map(), so you don't need to learn new syntax
  • You need to use a key prop — it helps React identify which item is which across renders, so it can update them correctly

JSX example JSX example JSX example

Components

Components are just JS functions, named with a capital letter.

A component should have one objective. If it has more than one, split it into multiple components.

Props

  • You can pass data into components
  • Types of data: strings, functions, numbers, JSX, objects
  • You can pass children / components

🔗 CodePen example

Fun fact: A React element is an object representation of a DOM node.

React element as object

Handling events

  • Make event handlers their own function — 🔗 CodePen
  • Name them handle… plus the name of the event
  • Reference vs invocation
    • References aren't run until called — that's a referenced function
    • Invocation calls the function each time it's re-rendered
    • 🔗 CodePen
  • Event handlers need to live inside the component
  • Moving them outside helps performance, since React doesn't have to recreate the function on each rerender — but you have to write more syntax
  • There are also useMemo and useCallback, which cache a function (or what it returns), removing the performance issue

Hooks

Special functions that allow you to get help from React.

  • They have to start with use…
  • They can't be called inside conditions, loops, or nested functions
  • You can create your own hooks

Preserving values and useState

The role of useState: to allow us to persist state across renders.

This count variable will not update in React. Does anyone know why?

Count example, broken Count example, broken

The count variable will not persist across renders, because plain locals are re-created every time the component re-renders.

React.useState

React re-renders a component whenever its state changes.

useState returns an array with two items: state and setState.

useState destructure

  • state = the current value of state
  • setState = whatever you provide will replace the existing state value

String / number

useState with primitives

Object

useState with object

Array

  • Add element Add element
  • Remove element Remove element
  • Update element Update element

Don't mutate state! Always create a new instance of the array or object when editing.

How can we turn our count example into one that persists the count value?

🔗 CodePen example

Lifting state up

<TodoList /> should manage state for <Todo />.

Rule of thumb: if you have state that multiple components depend on, lift the state up so it can be shared across them.

If a child needs to update the parent's state, you can pass down a function for it to do so. Create an update function in the parent (where the state lives) and, via props, invoke that function from the child component(s) where the event handler(s) live.

Renders

The process of rendering

Rendering is the process of React asking your components to describe what they want their section of the UI to look like, now, based on the current combination of props and state.

  • React creates a snapshot of your component which captures everything (props, state, event handlers, and a description of the UI based on data)
  • JSX syntax is converted into React.createElement() calls at compile time
  • That becomes a tree of "element" objects with { type, props, children } (the virtual DOM)
  • React continues calling components to collect the entire new element tree
  • React then diffs the previous element tree against the current one to decide what actual changes are necessary

Render phase

  • Loops over the entire component tree, finds components marked for updates
  • Calls components to render them, and collects the element tree

Commit phase

  • All DOM updates are applied
  • Layout effects run (useLayoutEffect)

The useEffect hooks run after a short timeout after the commit phase.

Queuing renders

React will only re-render when state and props change.

  • React state can be defined by useState, useReducer, or useContext
  • You can force a rerender by using useReducer

Tip: Some people think rendering implies there must be an update to the DOM. But really, rendering is React working out if there needs to be an update to the DOM.

Rendering isn't a bad thing — it's how React knows it needs to change.

If you pass the same value to state when calling setState, React will compare both values, see there's no difference, and skip the re-render.

React rendering rules

Rules React components need to follow when rendering. These were articulated by Sebastian Markbåge, a lead engineer at Meta.

  • Rendering must be pure and have no side effects
  • Render logic must not:
    • Mutate a variable binding or a property on an object, unless that binding was "newly created"
function Foo() {
  let x = { active: false };
  let y = 0; // useRef(0) instead
  let clickHandler = () => {
    x.active = true; // not ok
    y++;             // not ok
  };
  return <div onClick={clickHandler} />;
}
  • Call Math.random() or Date.now(), since these read values that later change
  • Issue a network request (POST), file system, or other I/O that writes to a persistent store. E.g. impression logging, creates, updates, or deletes
  • Create a new component type — a new function/class that is later used in JSX
  • Call another function that might do any of the above
  • Render logic may:
    • Mutate objects and bindings, as long as that object is "newly created"
    • Issue a network request (GET), a read from the file system, or other I/O is OK if it doesn't cause a write, and as long as the result is not read by the pure function. (Why would you load something you're not going to read? It can be used to prime a cache, with the read happening from the cache.)
    • Read and mutate a value for the purpose of lazy initialization (this is an exception to the rules above)
    • Throw errors

"Side effects" — anything that affects the state of the world outside of this function as it runs.

"Newly created" — the object, or the closure around a variable binding, was created inside the pure function call itself. It's not enough that the object was created by a previous invocation of the function; it has to be within each call.

Component types and reconciliation

React reuses existing component instances and DOM nodes as much as possible. To speed up reconciliation:

  • It compares old and new component types at a location
  • If prevElement.type !== currElement.type, it assumes the entire subtree will be different

That means React will destroy that entire existing component subtree, including all DOM nodes, and recreate it from scratch with new component instances.

For correct behavior, you must never create new component types while rendering, because that creates new references!

Reconciliation pitfall

Keys

Keys allow React to identify instances of components or JSX elements. They ensure each item is updated correctly across renders.

So you should always add a key prop to components/JSX elements when rendering an array of items:

todos.map(todo => <TodoListItem key={todo.id} todo={todo} />)

Event handlers run before re-render

When an event handler runs, props and state hold the values they had before the event — React hasn't re-rendered yet.

"Async" rendering

🔗 CodePen

React rendering is async. The actual render is sync, but it happens later, at the end of the event loop. The event handler has access to props and state as they were at the moment the snapshot/render was created.

setState with the same value

🔗 CodePen

When handleClick runs, it calculates the new state value to be 0, which is the same as the current state. So React doesn't trigger a re-render.

Batching multiple invocations

🔗 CodePen

React batches multiple state updates within the same event handler. Only the last value matters. React will only re-render once per event handler, even if that handler updates multiple pieces of state.

Re-render example

🔗 CodePen

If React sees that state has changed after an event handler, it will also re-render all child components.

Things you should know about rendering

  • React batches multiple state updates — only the last one in a given event handler counts
  • When a parent component re-renders, all of its child components also re-render, regardless of whether they accept any props (remember the component tree)
  • Rendering is rarely the source of performance issues; it's usually how the code is written. Use memo, useMemo, and useCallback if performance becomes a problem

Side effects → useEffect()

useEffect is for encapsulating a side effect that synchronizes your component with some outside system.

  • It works by removing the side effect from React's rendering flow and delaying its execution until it's safe to run — after the component has been rendered
  • useEffect will invoke its effect once after the initial render, and again after every render in which a value in its dependency array has changed

A function that calculates the view needs to be a pure function. When calling the component, React should be able to get a description of the UI without running into side effects.

What are side effects?

A function has a side effect any time it does anything other than take input arguments and calculate a return value:

  • API calls
  • DOM manipulation
  • Browser APIs such as localStorage and setTimeout
  • Anything else that falls outside calculating a view based on props and state

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.

You can't call a side effect during render — only inside an event handler or useEffect. Even if it's only used for the initial state.

Referential equality in JS

All variables can store one of two types of data.

Primitive values Reference values
Number, String, Boolean, Undefined, Null Object, Array, Function

If you looked at the in-memory value of a primitive, you'd see the actual value:

let name = 'Daniel';
// 0x01 -> 'Daniel'

But if you looked at a reference value, you'd see a reference (a pointer) to a spot in memory:

let person = { name: 'Daniel' };
// 0x01 -> 0x07
const name = 'Daniel';
const nameEqualsTheSame = name === 'Daniel'; // TRUE

const person = {};
const personEqualsTheSame = person === {};   // FALSE

🔗 Examples on CodePen

useEffect

🔗 CodePen

  • Delay: removes the side effect from React's render flow and delays execution until after the component has been rendered
  • Predictability: React can maximize the speed and predictability of rendering by enforcing rules around when side effects can run — even when an event occurs, the side effect runs in an event handler or in useEffect
  • Invocation: useEffect runs once after the initial render, and runs again after every re-render — unless you provide a dependency array, in which case it only re-runs when one of those dependencies changes
  • Clean-up function:
    • Sometimes useEffect runs multiple times if a dependency changes before the previous effect has finished running
    • To prevent stale setState calls from racing each other, you can return a clean-up function that ignores changes from non-latest effects
    • useEffect calls the clean-up function each time before it runs the effect again, and once more when the component is removed from the DOM

🔗 CodePen

useRef

  • Create a value that's preserved across renders but won't trigger a re-render when it changes
  • Returns { current: null }
  • If you have a state value that doesn't affect the view, put it in a useRef
  • Allows us to reference a DOM node

When you pass a ref as a prop to a React element, React will return a reference to the DOM element as the current property.

Scroll an image into view using useRef:

🔗 CodePen

useContext

A global state management tool — React's built-in answer to global state.

  • Use useContext when sending data across a shallow depth of components
  • Use Redux when you want to send data across the entire application

useReducer

  • Similar to useState, but lets you manage state with a reducer function
  • Returns a state value and a dispatch function
  • Takes a reducer function and an initial value
  • dispatch calls the reducer function
  • dispatch takes a single argument — typically an action object with a type field

Why use useReducer over useState?

  • When updating state that depends on the value of another piece of state
  • Decouples how the state is updated from what triggered the update
  • Helps you call setState in places where you had trouble before — e.g. inside a useEffect hook, since you don't need to add the state value as a dependency
  • Helps you stay declarative

Using useReducer to increment and decrement a count:

🔗 CodePen

useMemo & memo

When state changes, it updates the component that owns that state and all its child components, regardless of whether those child components accept any props.

memo lets us ensure that a component only re-renders when its props change.

If you pass reference values to a memo-wrapped component, it will re-render every time anyway. Why?

Because the reference value is being recreated each time the parent component renders:

{} === {} // false

useMemo lets us cache a reference value across renders, so it doesn't get recreated each time. That keeps the memo-wrapped child from re-rendering unnecessarily.

  • Rendering challenge with memo — 🔗 CodePen
  • Rendering challenge without memo — 🔗 CodePen

useLayoutEffect

useEffect runs async after the initial render. That's good for 99% of cases.

If you want to run logic during the initial render — before the browser paints the screen — use useLayoutEffect.

Example of when to use it: you have a side effect that synchronizes layout information with your component.

Rule: if a side effect is synchronizing your component with some outside system and it needs to run before the browser paints the screen, put that side effect inside useLayoutEffect.

useSyncExternalStore

If state is being managed outside of React, you can either add an event listener and update React state from it, or use useSyncExternalStore to read and subscribe to the external state directly.

Rule: if a side effect is subscribing to an external store, use useSyncExternalStore.

useSyncExternalStore example

Creating custom hooks

What makes a hook?

  • It has use at the start of the function name
  • It uses at least one hook inside it

Custom hook example:

🔗 CodePen


License

This article is released under the MIT License. If it helped you, a ⭐ would mean a lot.

About

A practical, opinionated 2025 intro to React — JSX, components, hooks, rendering rules, and the why behind them.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors