Overview

benefits

  • break down the UI codebase into individual, manageable files.
  • describe the UI with HTML-like markup within Javascript files (JSX/TSX).
  • import and consume third-party components.
  • entrust React to manage the DOM and keep it synced with data and state.

JSX

overview

JSX is a variant of JS that allows HTML-like snippets:

const HiText = <div>Hi</div>

We may interweave JS and HTML snippets recursively

const HiText = <div>Hi {nickname}</div>

start an html-like snippet

HTML-like syntax starts at the first HTML element and ends on its matching closing element.

const HiText = <div>Hi</div>

embed a JS snippet

Within the snippet, we may embed some JS within curly braces.

const HiText = <div>Hi {nickname}</div>
const HiText = <div style={st}>Hi</div>

transpile

A transpiler transforms the HTML-like snippets into JS code. An HTML element is transformed into a single function call.

const hi = <div>Hi</div>
const hi = _jsx("div", { children: "Hi" })

tsx

TSX is the file format for Typescript files that contain HTML-like snippets in the same fashion as JSX.

React component

A React component describes a piece of markup.

  • It may receive data from a parent component and make use of it.
  • It may declare some state that is to be scoped to this component. Scoping state to the component makes it easier to keep parents unaffected by any change in state.
  • Finally, a React component may import and use other components.
// describe a piece of UI
function Home() {
    return <div>Hi</div>
}
// encapsulate some state
function Counter() {
    const [count, setCount] = React.useState<number>(0)
    return <div>{count}</div>
}
// use other components
import { Header, Footer } from "./components"
function Home() {
    return (
        <>
            <Header />
            // ...
            <Footer />
        </>
    )
}

Props object

a container for props

Since the component is a function, we must decide which parameters to declare.

We technically don't need to declare a parameter. In that case, the component produces the same markup whenever used.

If we instead do expect something from the caller, the pattern is for React to gather up everything in a single object parameter. By convention, we call this object props, simply because it contains the props passed down by React.

function Home(props: Object) {}

a contract

We usually set up a contract between the component and its caller: The component declares a list of props it expects to receive. It is then up to the caller to provide them, in the form of custom attributes.

In this example, the contract is about a single prop called x of type number:

function Home(props: { x: number }) {
    // props.x
}

// caller
;<Home x={3} />

the children prop

When the caller nests some elements inside, React packs them into a special prop called children, whose type is ReactNode.

<Container>
    <div>Hello</div>
    <div>World!</div>
</Container>

The pattern is used for a Container component, which has to work with some piece of markup: We prefer to provide the markup in the form of nested JSX, rather than through an attribute that would be called children.

function Container(props: { children: ReactNode }) {
    return (
        <>
            {" "}
            // perform a specific layout
            {children}
        </>
    )
}

useState

We request React to create a state variable and give us a view on it as well as a dispatch method, which allows us to request state changes. It is up to React to account for such requests and potentially update the document

initial value

useState<number>(0)

receive the reference and a dispatch method

const [count, setCount] = React.useState<number>(0)

call the dispatch method

In the simplest form, the dispatch method expects a raw value.

setCount(1)

If we want to derive a value from the state, we may either refer to the state value we have in scope, or otherwise a state value given as part of a callback function.

setCount(count + 1)
setCount((count) => count + 1)

useRef

controlled vs uncontrolled element

In principle, React manages the DOM elements. Changes in the DOM derive from a change in state. This is the pattern used for controlled elements.

Sometimes, we want a DOM element to evolve independently (uncontrolled element), its value not being derived from React's state. This pattern avoids React re-renders when the element changes, and is useful when the element changes frequently and when re-renders would affect large parts of the DOM.

Such pattern is called using an uncontrolled (DOM) element.

useRef allows requesting a reference to such an uncontrolled element, so that we may read or set its value at arbitrary time using regular DOM APIs.

Such a reference persists across renders and is only discarded when the DOM element unmounts.

get a reference to an uncontrolled DOM element

In this example, we request a reference to the <input> DOM element, so that keystrokes do not trigger a React re-render.

const myInput = useRef<HTMLInputElement>(null)
// ..
<input ref={myInput}/>

To get better autocomplete later on when we use the reference, We indicate the element's DOM type as a type parameter. The type can be as broad as HTMLElement, but in this example it is narrowed down to the more accurate HTMLInputElement.

We don't need to indicate null as a type parameter. React already adds it on our behalf.

use the reference

the current property starts as null because there is no way to get a valid DOM reference before the element is mounted. Later, React assigns the valid DOM reference to current.

myInput.current // the DOM element

// use DOM APIs (not React specific)
myInput.current.innerText
myInput.current.innerHTML
myInput.current.value

other uses

By extension, we may use useRef to hold any kind of value that persists across renders, and that is to be discarded on unmount. For example, we may store an internal counter that is incremented when the component renders. Because it is not part of the React's state, its incrementation does not trigger a re-render.

useEffect

An effect is a function tied to a specific component which runs on specific events or conditions.

non-blocking

The effect never delays the component being painted on-screen: neither the first paint nor the subsequent ones. Instead, effects start running after paints. This pattern aims to prioritize time-to-paint.

If the effect fetches data that is to be displayed on-screen, we have to deal with a first paint which won't have the data. We may either use a default value, or hide the value altogether by displaying a loader, a skeleton or nothing instead.

one or multiple runs

An effect runs at least once, after the first paint. The subsequent runs are conditionals. We may ask for the effect to run again:

  • on every render
  • or when one or several variables change in value, usually because they act as a dependency of that effect.

clean-up on unmount

The component may unmount at any time. We may perform clean-up in certain conditions:

  • we have set-up a subscription and we want to cancel it.
  • we have initiated a network fetch, and we want to disable the callback. We do that by setting a flag such as isMounted to false, and ensures the callback does not mutate state when such flag is false.

synopsis

useEffect(f, [])

synchronous function

Even though the effect may start asynchronous tasks, it must itself come as a non-async function, that is, it must run and return immediately. As such, we may not use await directly in its body, but we may define an async function which uses await in its body and call that function instead.

function myEffect() {
    /* effect content */
    return /* clean up content */
}

dependencies

useEffect(effect, []) // once
useEffect(effect, [x]) // once, then when x changes
useEffect(effect) // on every render
earlymorning logo

© Antoine Weber 2026 - All rights reserved

Overview

benefits

  • break down the UI codebase into individual, manageable files.
  • describe the UI with HTML-like markup within Javascript files (JSX/TSX).
  • import and consume third-party components.
  • entrust React to manage the DOM and keep it synced with data and state.

JSX

overview

JSX is a variant of JS that allows HTML-like snippets:

const HiText = <div>Hi</div>

We may interweave JS and HTML snippets recursively

const HiText = <div>Hi {nickname}</div>

start an html-like snippet

HTML-like syntax starts at the first HTML element and ends on its matching closing element.

const HiText = <div>Hi</div>

embed a JS snippet

Within the snippet, we may embed some JS within curly braces.

const HiText = <div>Hi {nickname}</div>
const HiText = <div style={st}>Hi</div>

transpile

A transpiler transforms the HTML-like snippets into JS code. An HTML element is transformed into a single function call.

const hi = <div>Hi</div>
const hi = _jsx("div", { children: "Hi" })

tsx

TSX is the file format for Typescript files that contain HTML-like snippets in the same fashion as JSX.

React component

A React component describes a piece of markup.

  • It may receive data from a parent component and make use of it.
  • It may declare some state that is to be scoped to this component. Scoping state to the component makes it easier to keep parents unaffected by any change in state.
  • Finally, a React component may import and use other components.
// describe a piece of UI
function Home() {
    return <div>Hi</div>
}
// encapsulate some state
function Counter() {
    const [count, setCount] = React.useState<number>(0)
    return <div>{count}</div>
}
// use other components
import { Header, Footer } from "./components"
function Home() {
    return (
        <>
            <Header />
            // ...
            <Footer />
        </>
    )
}

Props object

a container for props

Since the component is a function, we must decide which parameters to declare.

We technically don't need to declare a parameter. In that case, the component produces the same markup whenever used.

If we instead do expect something from the caller, the pattern is for React to gather up everything in a single object parameter. By convention, we call this object props, simply because it contains the props passed down by React.

function Home(props: Object) {}

a contract

We usually set up a contract between the component and its caller: The component declares a list of props it expects to receive. It is then up to the caller to provide them, in the form of custom attributes.

In this example, the contract is about a single prop called x of type number:

function Home(props: { x: number }) {
    // props.x
}

// caller
;<Home x={3} />

the children prop

When the caller nests some elements inside, React packs them into a special prop called children, whose type is ReactNode.

<Container>
    <div>Hello</div>
    <div>World!</div>
</Container>

The pattern is used for a Container component, which has to work with some piece of markup: We prefer to provide the markup in the form of nested JSX, rather than through an attribute that would be called children.

function Container(props: { children: ReactNode }) {
    return (
        <>
            {" "}
            // perform a specific layout
            {children}
        </>
    )
}

useState

We request React to create a state variable and give us a view on it as well as a dispatch method, which allows us to request state changes. It is up to React to account for such requests and potentially update the document

initial value

useState<number>(0)

receive the reference and a dispatch method

const [count, setCount] = React.useState<number>(0)

call the dispatch method

In the simplest form, the dispatch method expects a raw value.

setCount(1)

If we want to derive a value from the state, we may either refer to the state value we have in scope, or otherwise a state value given as part of a callback function.

setCount(count + 1)
setCount((count) => count + 1)

useRef

controlled vs uncontrolled element

In principle, React manages the DOM elements. Changes in the DOM derive from a change in state. This is the pattern used for controlled elements.

Sometimes, we want a DOM element to evolve independently (uncontrolled element), its value not being derived from React's state. This pattern avoids React re-renders when the element changes, and is useful when the element changes frequently and when re-renders would affect large parts of the DOM.

Such pattern is called using an uncontrolled (DOM) element.

useRef allows requesting a reference to such an uncontrolled element, so that we may read or set its value at arbitrary time using regular DOM APIs.

Such a reference persists across renders and is only discarded when the DOM element unmounts.

get a reference to an uncontrolled DOM element

In this example, we request a reference to the <input> DOM element, so that keystrokes do not trigger a React re-render.

const myInput = useRef<HTMLInputElement>(null)
// ..
<input ref={myInput}/>

To get better autocomplete later on when we use the reference, We indicate the element's DOM type as a type parameter. The type can be as broad as HTMLElement, but in this example it is narrowed down to the more accurate HTMLInputElement.

We don't need to indicate null as a type parameter. React already adds it on our behalf.

use the reference

the current property starts as null because there is no way to get a valid DOM reference before the element is mounted. Later, React assigns the valid DOM reference to current.

myInput.current // the DOM element

// use DOM APIs (not React specific)
myInput.current.innerText
myInput.current.innerHTML
myInput.current.value

other uses

By extension, we may use useRef to hold any kind of value that persists across renders, and that is to be discarded on unmount. For example, we may store an internal counter that is incremented when the component renders. Because it is not part of the React's state, its incrementation does not trigger a re-render.

useEffect

An effect is a function tied to a specific component which runs on specific events or conditions.

non-blocking

The effect never delays the component being painted on-screen: neither the first paint nor the subsequent ones. Instead, effects start running after paints. This pattern aims to prioritize time-to-paint.

If the effect fetches data that is to be displayed on-screen, we have to deal with a first paint which won't have the data. We may either use a default value, or hide the value altogether by displaying a loader, a skeleton or nothing instead.

one or multiple runs

An effect runs at least once, after the first paint. The subsequent runs are conditionals. We may ask for the effect to run again:

  • on every render
  • or when one or several variables change in value, usually because they act as a dependency of that effect.

clean-up on unmount

The component may unmount at any time. We may perform clean-up in certain conditions:

  • we have set-up a subscription and we want to cancel it.
  • we have initiated a network fetch, and we want to disable the callback. We do that by setting a flag such as isMounted to false, and ensures the callback does not mutate state when such flag is false.

synopsis

useEffect(f, [])

synchronous function

Even though the effect may start asynchronous tasks, it must itself come as a non-async function, that is, it must run and return immediately. As such, we may not use await directly in its body, but we may define an async function which uses await in its body and call that function instead.

function myEffect() {
    /* effect content */
    return /* clean up content */
}

dependencies

useEffect(effect, []) // once
useEffect(effect, [x]) // once, then when x changes
useEffect(effect) // on every render