In the Washington Post, Michael Gerson summarizes the career of conservative favorite Alex Jones at InfoWars:

“At various points, Jones has promoted the belief that 9/11 was an ‘inside job,’ that Hillary Clinton was running a child sex ring out of a pizzeria, that NASA had built a child slave colony on Mars in order to harvest blood and bone marrow, that the Oklahoma City bombing, the Boston Marathon bombing and the Sandy Hook school shooting were government ‘false flag’ operations, that some shooting survivors were ‘crisis actors,’ that ‘globalists’ are intent on committing genocide and that Democrats are on the verge of launching a second civil war.”

There are some ridiculous and unlikable people on the left, but I’ve never seen anyone like that.

React Notes

Front-end web framework notes — React 17

React Notes

These are my React notes, covering React 17. React native development is not included here. If you find a mistake, please let me know.

The example code uses a new notation I am developing. More on that later.

This page includes a two-column print style. For best results, print in landscape, apply narrow margins, change the Scale setting in your browser’s print options to 70%, and enable background graphics. Firefox and Chrome each have their own set of printing bugs, but one of them usually works.

Contents

create-react-app

create-react-app or CRA is an npm package from Facebook that creates and configures React projects. This single dependency installs and manages a number of other packages, including:

  • Babel, which is used to transpile JSX and newer JavaScript syntax into browser-ready JavaScript;
  • Webpack, which is used to manage module exports and imports, and to bundle imported code into a small number of files that produce fewer requests. It also produces source maps, which correlate transpiled, minified, and bundled code in the browser with unprocessed code, for display when debugging;
  • Jest, which is used to run tests.

If it becomes necessary to configure these packages directly, CRA can eject the project, converting it to a conventional installation with discrete dependencies. Once ejected, the project cannot be managed again with CRA.

Creating a project

To create project name within folder name:

npx create-react-app name

Running CRA with npx ensures that the latest version is used. The --template switch can be added to select common project configurations:

cra-template
The default template.
cra-template-typescript
Creates a TypeScript project.
cra-template-pwa
Creates a Progressive Web App project.
cra-template-pwa-typescript
Creates a Progressive Web App project with TypeScript.

Many third-party templates are distributed by npm.

Running the development build

To start the development server and run the development build:

cd name
npm start

By default, the build is served to http://localhost:3000/. In most cases, the page reloads automatically when code is updated.

CRA configures the project with ESLint, which displays warnings in the console that runs npm start, in the VS Code Problems tab, and in the Console tab within the browser DevTools. Specific warnings can be disabled by adding rules properties to the eslintConfig block in package.json:

"rules": {
  "no-unused-vars": "off"
}

This disables warnings immediately in VS Code. The warnings are not disabled in the browser until the development server is restarted.

Running tests

To run tests:

npm test

Deploying the project

To compile the production build:

npm run build

The build output is stored in the build/ folder. Bundled JavaScript and CSS files are stored in build/static/js/ and build/static/css/; these files are also listed within build/asset-manifest.json, which is compiled automatically. Webpack adds cache-busting hashes to the bundle filenames, and updates these automatically when bundled content changes.

Development with CRA

Project structure

Files in the public/ folder are copied to the build/ folder at compile time. They are not minified, nor are cache-busting hashes added to their names. Unlike src/ files, they cannot be targeted with import; they are meant to be served directly, or referenced from other public/ files with the %PUBLIC_URL% environment variable, which is replaced at compile time with an absolute path:

<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

This path is blank by default, so public files are served from the root of the domain. The path can be changed by adding a homepage entry to package.json:

"homepage": "https://www.anthemion.org/play-ogle"

However, only the path in this value is reproduced in %PUBLIC_URL%; the protocol, host, and domain are omitted.

In JavaScript, the public path can be read from process.env.PUBLIC_URL.

CRA adds these files to public/, among others:

index.html
The default page for the app, which is served to the browser before being populated with React output.
manifest.json
A basic web app manifest, which stores Progressive Web App metadata to be used when the app is installed on a mobile device. The file is referenced by a manifest link in the index.html head.

The src/ folder contains index.js, which writes top-level component output to index.html. The folder also stores JavaScript and CSS files that are used to implement components, plus assets referenced by components. All JavaScript imports target files relative to the src/ folder, whether these are modules, CSS files, images, or other imports.

Importing modules

CRA configures Webpack to use ECMAScript module syntax for exports and imports.

Normally, Node.js interprets files with the JS extension as CommonJS modules. Projects that use ECMAScript modules are expected to use the MJS extension, or alternatively, to add:

"type": "module"

to the top level of the package.json file. This is not necessary in CRA projects, because module exports and imports are processed by Webpack. In fact, using the MJS extension in a CRA project produces confusing compile-time errors like Uncaught TypeError: undefined is not a function and Can't import the named export 'jsxDEV' from non EcmaScript module.

Note that the React DevTools Components page reads component names from the classes or functions that define them, not from the names by which they may have been imported. For this reason, even default component exports are expected to be named, and ESLint will warn if an anonymous component is exported.

Code-splitting

Webpack automatically code-splits modules that are loaded dynamically:

import("./Mod.mjs").then(aMod => {
  aMod.uStart();
  ...
});

If a component is loaded this way, it must be the default export of its module, and it must be wrapped on the import side with React.lazy, which accepts a function that performs the import:

const Grid = React.lazy(() => import("./Grid.mjs"));

The wrapped component can then be nested within a Suspense component, which offers a fallback attribute that displays arbitrary JSX while the component loads:

const oGrid = (
  <Suspense fallback={<div class="FallLoad"></div>}>
    <Grid />
  </Suspense>
);

Importing CSS

Webpack allows CSS files to be used with import. This adds the referenced file to the CSS bundle, much the way a module import adds to the JavaScript bundle:

import "./Nav.css";

Though developers often create separate CSS files for each component, the styles in imported files are available throughout the project. They are not specific to the importing module.

CRA creates src/index.css automatically, and imports it within src/index.js.

Importing other files

Webpack allows images and other files to be used with import. When an image is imported:

import PathImgLogo from "./ImgLogo.png";

the file is added to the bundle, and a path is returned that can be used to reference it in JSX:

<img src={PathImgLogo} alt="" />;

For cache-busting purposes, Webpack adds hashes to the paths of bundled files, and changes these automatically when their content changes.

Webpack also bundles images that are referenced in CSS, potentially inlining them within the CSS itself as base64:

nav div.Logo {
  background-image: url(./ImgLogo.png);
}

It is also possible to import JSON data. The file content is parsed automatically and assigned to the import variable:

import Words from "./Words.json";

Webpack can be configured to import other formats, including XML and CSV.

JSX

JSX is a markup language that defines page content declaratively. At compile time, Babel converts JSX blocks into React.createElement expressions, which later generate ReactElement instances. React uses these to update its virtual DOM, and ultimately, the browser content.

JSX resembles HTML, but it differs in small ways. React uses the DOM API to update page content, so element attributes are set with DOM property names, not HTML attribute names. In particular, element classes are set with className, rather than class. Properties are generally named with camelCase, rather than all lowercase, so onclick becomes onClick. ARIA and HTML data attributes continue to use kebab case, however.

It is not possible to assign CSS strings to the style attribute in JSX; an object must be embedded instead. CSS properties are identified in this object with camelCased versions of the usual CSS property names:

const oCSS = {
  color: "white",
  backgroundColor: "black"
};

return <div style={oCSS}>...</div>;

Empty elements like br must be represented with self-closing tags, even though these are optional in HTML5:

<br />

JSX blocks are often parenthesized to avoid ASI problems:

return (
  <h2>Tasks</h2>
);

Embedded expressions

A JavaScript expression can be embedded in JSX by surrounding it with curly braces:

const oElID = (
  <div>Block {"ID" + oID}</div>
);

Many expression values are converted automatically to strings; React escapes these and other string values before embedding them. There is no need to use quotes when assigning string results to attributes:

<div id={oID}>

In fact, quoting the placeholder would cause the braces and the contained expression to be interpreted literally.

Expressions can return JSX, which is then embedded in the surrounding content. Along with the ternery operator, operators like && are sometimes used to embed conditional JSX expressions:

<section>
  <div className="CtAct">
    {oCt} active
  </div>
  {oCkWarn &&
    <div className="CtWarn">
      {oTextWarn}
    </div>
  }
</section>

Embedding an array causes each element to be embedded in sequence. Embedding a non-array object causes an exception to be thrown.

These values produce no page output when embedded:

  • true
  • false
  • null
  • undefined

Event handling

In HTML, an event handler is defined with a string that contains inlined JavaScript. The handler is invoked directly, with parentheses, within the string:

<button onclick="uHandReady()">Ready</button>

In JSX, the handler is not invoked directly; it is passed as a function reference, within a placeholder. Unlike HTML attributes, React events are named with camelCase:

<button onClick={uHandReady}>Ready</button>

The handler receives a SyntheticEvent instance when it is invoked. This object provides the same interface that a DOM event object would, but it is not the same type. The DOM event object can be obtained from the nativeEvent property within the SyntheticEvent.

To cancel a button’s default behavior, the handler must call preventDefault. It is not enough to return false:

function uHandSubmit(aEvt) {
  aEvt.preventDefault();
  ...
}

If the handler is a method of the component class, it must be bound to the class instance, or this will be defined incorrectly when the handler is invoked through the reference. This can be done by overwriting class methods in the constructor, after wrapping them with bind:

class Btn extends React.Component {
  constructor(aProps) {
    super(aProps);

    this.uHandClick = this.uHandClick.bind(this);
  }

  uHandClick(aEvt) {
    ...
  }

  render() {
    return (
      <button onClick={this.uHandClick}>
        {this.props.Text}
      </button>
    );
  }
}

Handlers can also be wrapped with arrow functions that capture this in a closure:

this.uHandClick = (aEvt) => this.uHandClick(aEvt);

The wrapper can be defined in the component render function, but this creates a new wrapper every time the component is rendered, which could affect performance:

<button onClick={(aEvt) => this.uHandClick(aEvt)}>

If a non-event argument is required, that may be unavoidable. If the handler is wrapped with an arrow function, the event object must be forwarded in the wrapper definition:

<button onClick={(aEvt) => this.uHandClickNum(aNum, aEvt)}>
  {aNum}
</button>

If it is wrapped with bind, this must be forwarded in the wrapper definition, but the event object can be ignored, since a bound function automatically forwards additional arguments to the wrapped function:

<button onClick={this.uHandClick.bind(this, aNum)}>
  {aNum}
</button>

React event handlers

Handlers can target events in the bubbling phase, or in the capture phase. Commonly-used bubbling event handlers are listed below. The same events can be intercepted during capture by appending Capture to these names.

Page events
  • onFocus
  • onBlur
  • onScroll
Form events
  • onChange
  • onInput
  • onInvalid
  • onReset
  • onSubmit
Text input selection events
  • onSelect
Mouse events
  • onClick
  • onContextMenu
  • onDoubleClick
  • onDrag
  • onDragEnd
  • onDragEnter
  • onDragExit
  • onDragLeave
  • onDragOver
  • onDragStart
  • onDrop
  • onMouseDown
  • onMouseEnter
  • onMouseLeave
  • onMouseMove
  • onMouseOut
  • onMouseOver
  • onMouseUp
  • onWheel
Touch events
  • onTouchCancel
  • onTouchEnd
  • onTouchMove
  • onTouchStart
Pointer events
  • onPointerDown
  • onPointerMove
  • onPointerUp
  • onPointerCancel
  • onGotPointerCapture
  • onLostPointerCapture
  • onPointerEnter
  • onPointerLeave
  • onPointerOver
  • onPointerOut
Keyboard events
  • onKeyDown
  • onKeyPress
  • onKeyUp
Clipboard events
  • onCopy
  • onCut
  • onPaste
CSS transition events
  • onTransitionEnd
CSS animation events
  • onAnimationStart
  • onAnimationEnd
  • onAnimationIteration
Media events
  • onAbort
  • onCanPlay
  • onCanPlayThrough
  • onDurationChange
  • onEmptied
  • onEncrypted
  • onEnded
  • onError
  • onLoadedData
  • onLoadedMetadata
  • onLoadStart
  • onPause
  • onPlay
  • onPlaying
  • onProgress
  • onRateChange
  • onSeeked
  • onSeeking
  • onStalled
  • onSuspend
  • onTimeUpdate
  • onVolumeChange
  • onWaiting
Other events
  • onError
  • onLoad

Components

React components are reusable structures that generate ReactElement instances, representing page output. Functions in the react-dom module translate these instances to DOM elements in the browser. react-native translates them to native controls in various desktop and mobile platforms.

React expects component names to be capitalized; names with lowercase initials are assumed to be DOM elements. Components are referenced in JSX much the way DOM elements are:

const oLinks = (
  <ul>
    <ItLink Addr="/sec1" Text="Section 1" />
    <ItLink Addr="/sec2" Text="Section 2" />
    <ItLink Addr="/sec3" Text="Section 3" />
  </ul>
);

Component definitions must be in scope for the referencing JSX. When their definitions are nested within objects:

const Graphs = {
  Bar: function (aProps) {
    ...
  },
  Pie: function (aProps) {
    ...
  }
};

those definitions must be dereferenced with the JavaScript ‘dot’ syntax:

<div>
  <Graphs.Bar />
  <Graphs.Pie />
</div>

Component names are always interpreted as function or class references. Component types can be selected conditionally by assigning a reference to a variable, and then using the variable name as a component. The name must be capitalized, however:

function DispGraph(aProps) {
  const Graph = aProps.CkPie ? Graphs.Pie : Graphs.Bar;
  return (
    <div>
      <Graph />
    </div>
  );
}

In like manner, when components are passed to functions, the parameter names can be used as components:

function BtnLbl(Btn, Lbl) {
  return (
    <div>
      <Btn /><Lbl />
    </div>
  );
}

Function components

Components can be implemented as functions or classes. A function component resembles the render function found in a class component. It accepts a single props object argument that stores any prop attributes that were assigned when the component was invoked. It returns a JSX expression:

function ItLink(aProps) {
  return <li><a href={aProps.Addr}>{aProps.Text}</a></li>;
}

or an array of JSX expressions, to be rendered in sequence within the component’s parent element:

function ItsLink(aProps) {
  return [
    <ItLink key="status" Addr="/status" Text="Status" />,
    <ItLink key="updates" Addr="/updates" Text="Updates" />,
    <ItLink key="settings" Addr="/settings" Text="Settings" />
  ];
}

If an array is returned, a sequence-unique key property should be assigned to each element. This value is not added to the component props. React uses keys to track changes within the sequence, so element indices should not be used as key values if elements can be deleted, or if the sequence can be reordered.

Keys must be assigned in the array itself:

<ul>
  {oNums.map(o => <It key={o.toString()} Num={o} />)}
</ul>

They cannot be assigned in the embedded component’s render function.

The component can return null if it generates no page content. Like other render functions, function components should not produce side effects.

Class components

Class components subclass React.Component:

import React from "react";

class ItLink extends React.Component {
  constructor(aProps) {
    super(aProps);
    ...
  }

  render() {
    return (
      <li>
        <a href={this.props.Addr}>{this.props.Text}</a>
      </li>
    );
  }
}

Class instances are created and managed by React, which supplies the props object as a constructor argument. If a custom constructor is defined, it must forward this parameter to the base constructor, which stores it in the props class property.

The subclass must define a render method. This returns a JSX expression, an array of JSX expressions, or null, just as a function component would.

The constructor and the render method may be called at any time during the render phase, so they should not produce side effects. Operations with side effects should be confined to the componentDidMount, componentDidUpdate, and componentWillUnmount methods. If the component must subscribe to an event published by another object, it can do so within componentDidMount.

Though props are passed to the constructor, props changes do not cause class components to be reinstantiated. They do cause the component to be re-rendered, and props is updated automatically before render is called.

Component props

Most attributes assigned to component instances:

<ItLink Addr="/" Text="Home" />

are combined into a props object that is forwarded to the component constructor and used to configure its output. This object is frozen, so properties cannot be added or changed.

A prop can be assigned any value, including a function or object reference. If an attribute is specified in JSX without a value, the corresponding prop will be set to true:

<BtnRadio CkDown />

It is better to set these values explicitly, however:

<BtnRadio CkDown={true} />

The spread syntax can be used to forward the properties of an object as distinct props:

function ItLink(aProps) {
  return <li><Link {...aProps}/></li>
}

Default props

Ordinarily, if a prop’s attribute is not assigned when the component is invoked, it will be undefined in the component’s render function. Default values can be specified by assigning an object to the component’s defaultProps property, however:

function Ship(aProps) {
  return <div>...</div>
}

Ship.defaultProps = {
  CdLoc: "PEND",
  CtFail: 0,
  CtRetry: 0
};

Prop type checking

React can also perform run-time type checking on component props. Type checks are defined by assigning an object to the component’s propTypes property:

import PropTypes from "prop-types";

function StatWare(aProps) {
  return (
    <section>
      <h3>Area {aProps.NumArea}</h3>
      <div>{aProps.Notes}</div>
    </section>
  );
}

StatWare.propTypes = {
  NumArea: PropTypes.number,
  Notes: PropTypes.string
};

This object contains properties that associate prop names with validators imported from the prop-types module:

bool number string symbol object array func
Matches any value with the specified JavaScript type.
node
Matches any type that can be rendered by React, including numbers, strings, and elements, plus arrays or fragments of such.
element
Matches a ReactElement instance.
elementType
Matches a component reference.
instanceOf(class)
Matches an object that inherits, directly or indirectly, from class.
oneOf(vals)
Matches any type, as long as the prop value is found within array vals. The vals elements can vary in type.
oneOfType(types)
Matches any of the PropTypes validators in array types.
arrayOf(type)
Matches an array if all of its elements match the given PropTypes validator.
objectOf(type)
Matches an object if all of its properties match the given PropTypes validator.
shape(patt)
Matches an object if none of its properties conflict with object patt, which maps property names to PropTypes validators. Extra object properties are ignored, as are missing properties.
exact(patt)
Functions as shape, but rejects objects that have extra properties. Missing properties are still ignored.

Note that complex validators like arrayOf can be used within other validators, such as oneOfType:

StatWare.propTypes = {
  // Allow a single number or an array of strings:
  Data: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.arrayOf(PropTypes.string)
  ]),
  NumArea: PropTypes.number,
  Notes: PropTypes.string
};

All validators define an isRequired property that acts as the same validator, while also warning if the associated property is undefined:

StatWare.propTypes = {
  NumArea: PropTypes.number.isRequired,
  ...
};

Custom validation functions can also be associated with props. Each function accepts a props object, a string containing the name of the prop being validated, and a string that gives the component name. The function should return an Error instance if the prop is invalid:

function Valid_PropNumShelf(aProps, aNameProp, aNameCompnt) {
  if (aProps.NumShelf
    && ((aProps.NumShelf < 1) || (aProps.NumShelf > NumShelfMax)))
    return new Error(aNameCompnt + ": Invalid shelf number")
}

StatWare.propTypes = {
  NumShelf: Valid_PropNumShelf,
  ...
};

Custom functions can also be passed to PropTypes.arrayOf or PropTypes.objectOf. These functions are invoked once for each array element or object property. They accept the props object, the index or key of the value being checked, the component name, an undocumented location parameter, and the full name of the property or element being checked.

Props that are not named in the propTypes object are not checked. Default prop values are not validated unless they are used by the component. For each check that fails, a warning is logged to the console. Checks are performed only during the development build.

Component state

A component’s props are defined in the JSX or other code that causes it to be instantiated. They are immutable within the component, so it cannot trigger updates by modifying them itself. If a control changes in response to user input, it must trigger an update by modifying its state.

A class component’s state is stored in an object referenced by the state property, defined by React.Component, from which the class inherits. This object can be assigned in the constructor, but direct updates are disallowed thereafter. Instead, updates are performed with the setState method, which is also inherited from React.Component. This method eventually causes state to be updated, which causes render to be invoked:

class Page extends React.Component {
  constructor(aProps) {
    super(aProps);
    this.state = { CkAlert: false };

    this.uShow_Alert = this.uShow_Alert.bind(this);
  }

  uShow_Alert() {
    this.setState({ CkAlert: true });
  }

  uBoxAlert() {
    if (!this.state.CkAlert) return null;
    ...
  }

  render(aProps) {
    return (
      <div>{this.uBoxAlert()}</div>
    );
  }
}

When an object is passed to setState, it is merged with the current state data; properties in the new state are made to overwrite those in the old, while properties in the old that were not specified in the new are left as-is.

The state update is asynchronous, so the new state object should not derive values from the current state, which may be out of date when the update occurs. If it is necessary to derive new from old, an update function can be passed instead:

this.setState((aState, aProps) => ({
  IDNext: aState.IDNext + 1
}));

This function accepts parameters representing the original state and the component props. It returns new state data, to be merged with the current state. It is invoked during the render phase, and it may be called more than once, so it should not produce side effects.

setState also accepts a callback as an optional second argument, to be invoked after the component is re-rendered.

While component updates are usually triggered by state changes, or by changes in the component’s parent, an instance can made to re-render by calling its forceUpdate method. This causes the shouldComponentUpdate lifecycle event to be skipped. It accepts an optional callback argument that is invoked after the forced update.

Component children

When elements are nested within a component’s start and end tags in JSX:

<Sidebar Head="Common problems">
  <ul>
    <li>Uninitialized pointers</li>
    <li>Null pointers</li>
    <li>Dangling pointers</li>
  </ul>
</Sidebar>

they are automatically assigned to the component’s children prop. This allows those children to be embedded within the component’s output:

function Sidebar(aProps) {
  return (
    <section className="Sidebar">
      <header>
        <h3>{aProps.Head}</h3>
      </header>
      {aProps.children}
    </section>
  );
}

It is also possible to pass non-JSX values as children, including functions and other objects. These are then referenced in the component by the children prop, just as a JSX expression would be. The React.Children object provides utility functions like React.Children.map that process these and other child elements.

If the containing component consumes multiple JSX expressions, they can be explicitly assigned to props:

<BoxCompare
  OptA={
    <div>
      <h2>Manual checks</h2>
      <p>Error-prone</p>
      <p>Verbose</p>
    </div>
  }
  OptB={
    <div>
      <h2>Automated checks</h2>
      <p>Resource-intensive</p>
      <p>Less flexible</p>
    </div>
  }
/>

and then read from the props at render time:

function BoxCompare(aProps) {
  return (
    <section className="BoxCompare">
      {aProps.OptA}
      <hr />
      {aProps.OptB}
    </section>
  );
}

As usual, each expression must define a single parent element; otherwise, fragments or JSX arrays must be passed.

Render props

The children prop allows the element structure defined by one component to be reused with different children. A render prop provides similar functionality, while also allowing the component to pass data to those children.

The render prop is an ordinary component prop, to which a function has been assigned. It is conventional (but not necessary) to name the prop render:

<Spin render={
  aVal => (<div>Current: {aVal}</div>)
}/>

Note that defining the function within the JSX causes a new function instance to be created each time the JSX is evaluated. This could affect performance, and it should not be combined with React.PureComponent, as that class uses reference equality to detect prop changes.

The function accepts whatever arguments the containing component wishes to provide, and returns a JSX expression, to be embedded by the component:

class Spin extends React.Component {
  constructor(aProps) {
    super(aProps);
    this.state = { Val: 0 };

    this.uDec = this.uDec.bind(this);
    this.uInc = this.uInc.bind(this);
  }

  uDec(aEvt) {
    this.setState(aState => ({ Val: aState.Val - 1}));
  }

  uInc(aEvt) {
    this.setState(aState => ({ Val: aState.Val + 1}));
  }

  render() {
    return (
      <div>
        {this.props.render(this.state.Val)}

        <button onClick={this.uDec}>-1</button>
        <button onClick={this.uInc}>+1</button>
      </div>
    );
  }
}

Fragments

Only one top-level element can be returned by a component. To return multiple elements without a container of some sort, wrap them in React.Fragment:

return (
  <React.Fragment>
    <div>LAND C</div>
    <div>LAND X</div>
  </React.Fragment>
);

This can also be written as:

return (
  <>
    <div>LAND C</div>
    <div>LAND X</div>
  </>
);

however, only the full React.Fragment syntax allows key attributes to be assigned to fragments, if the fragments themselves happen to be part of an array. key is the only attribute that can be assigned to a fragment.

Component context

Ordinarily, component configuration data is passed from ancestor elements to descendents via props, but this can be verbose when elements are deeply nested. The React context system allows data to be shared with descendents without forwarding props at each level.

Context data is stored within a context object, created with React.createContext. At render time, JSX will be used to assign values to this context, and descendents that subscribe to the context will read from it. React.createContext accepts a single argument that sets the default value, to be read when no value has been assigned by an ancestor in the JSX:

const ContextStat = React.createContext("Red");

The context object defines a Provider component with a value attribute that sets the context value:

<Box />
<ContextStat.Provider value="Green">
  <Box />
  <ContextStat.Provider value="Blue">
    <Box />
  </ContextStat.Provider>
</ContextStat.Provider>

This value is available to all components contained by the Provider, regardless of depth. When providers are nested, each value takes precedence over the ones above it. Subscribing components are updated when a value changes, even if their shouldComponentUpdate methods return false.

By default, in the React DevTools Components page, all provider components are listed as Context.Provider, regardless of the context object that defines them. The displayName property within the context object can be used to replace Context with a distinct name:

ContextStat.displayName = "ContextStat";

A class component subscribes to the context by assigning the context object to a class-static variable named contextType. At render time, it reads the current value from its own context property:

class Box extends React.Component {
  static contextType = ContextStat;

  render() {
    return (
      <div className={`Box ${this.context}`}>
        PEND
      </div>
    );
  }
}

In the past, a function component subscribed to the context by embedding the Consumer component defined within the context object. This component interprets its child as a render prop, which itself receives the current context value as an argument, and returns the content to be displayed within the Consumer:

function Box(aProps) {
  return (
    <ContextStat.Consumer>
      {aStat => (
        <div className={`Box ${aStat}`}>
          PEND
        </div>
      )}
    </ContextStat.Consumer>
  );
}

Now, function components can read context values by calling the useContext hook. This allows context data to be used without passing a render prop.

By calling useContext more than once (or by embedding multiple Consumer components) a function component can subscribe to multiple contexts. Class components cannot subscribe to more than one, so the context must store an object if multiple values are to be shared:

const ContextStat = React.createContext({ Sty: "Red", Text: "PEND" });

class Box extends React.Component {
  static contextType = ContextStat;

  render() {
    return (
      <div className={`Box ${this.context.Sty}`}>
        {this.context.Text}
      </div>
    );
  }
}

However, React compares objects by reference when checking for changes. If provider values are assigned in JSX with object literals:

<ContextStat.Provider value={{ Sty: "Green", Text: "READY" }}>

new value objects will be created every time the JSX is evaluated, the comparison will always fail, and unnecessary DOM updates will result. To avoid this, value objects should be stored in the component state, and referenced from the Provider element:

class App extends React.Component {
  constructor(aProps) {
    super(aProps);

    this.state = {
      StatReady: { Sty: "Green", Text: "READY" },
      StatAct: { Sty: "Blue", Text: "ACT" }
    };
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <Box />
          <ContextStat.Provider value={this.state.StatReady}>
            <Box />
            <Box />
            <ContextStat.Provider value={this.state.StatAct}>
              <Box />
            </ContextStat.Provider>
          </ContextStat.Provider>
        </header>
      </div>
    );
  }
}

Element refs

Ordinarily, page content is modified by passing new props to components, or by changing their state, causing them to be re-rendered. The React element tree then updates the DOM content. Parent components cannot call child component methods directly, because component instances are created and managed by React. Nor can parents access DOM element instances.

A React ref provides direct access to a React component or DOM element instance. Ref objects are created with React.createRef. This function accept no arguments, and is often called in a component constructor:

class InName extends React.Component {
  constructor(aProps) {
    super(aProps);

    this.RefIn = React.createRef();
    this.uFocus_In = this.uFocus_In.bind(this);
  }
  ...

At this point, the object references nothing. It is associated with a component or DOM element by assigning it to the target’s ref attribute during rendering:

render() {
  return <input name="Name" ref={this.RefIn} />;
}

The component or DOM element instance is then accessible through the current property in the ref object:

uFocus_In() {
  this.RefIn.current?.focus();
}

Until recently, function components were stateless, so they could not define refs. They can define them now with ref hooks.

Callback refs

A reference to a component or DOM element can also be obtained by assigning a callback function to the ref attribute:

<input name="Name" ref={this.uSet_ElIn} />

The callback is invoked when the component is mounted or unmounted. It accepts a single parameter that references the new element, or null, as appropriate:

uSet_ElIn(aEl) {
  this.ElIn = aEl;
}

If the callback is a class method, it should be bound to this, like other event handlers:

constructor(aProps) {
  super(aProps);

  this.uSet_ElIn = this.uSet_ElIn.bind(this);
  this.uFocus_In = this.uFocus_In.bind(this);
}

The reference itself is passed to the callback, so there is no current property:

uFocus_In() {
  this.ElIn?.focus();
}

Forwarded refs

A parent component cannot assign a ref attribute to a component or DOM element unless the target is defined in the parent’s render function. The component that does render the target can forward a ref it has received, however, allowing the parent to reference an element it does not render directly.

React.forwardRef creates a special function component that forwards a ref to one of its own children. It accepts a callback that itself resembles a function component; the callback accepts props and ref arguments, and returns component content. Within the callback, the ref is assigned to the target’s ref attribute as usual. React.forwardRef then returns the forwarding component:

const InDock = React.forwardRef((aProps, aRef) => (
  <input name={"InDock" + aProps.ID} ref={aRef} />
));

class BoxDoc extends React.Component {
  constructor(aProps) {
    super(aProps);

    this.RefIn = React.createRef();
    this.uReady = this.uReady.bind(this);
  }

  uReady() {
    this.RefIn.current?.focus();
    ...
  }

  render(aProps) {
    return (
      <div>
        <InDock ref={this.RefIn} />
        ...
      </div>
    );
  }
}

Class components cannot be passed to React.forwardRef. They can be wrapped in a function component that forwards the reference through a prop, however. The forwarding prop cannot be named ref, as that attribute is handled specially by React:

const InDock = React.forwardRef((aProps, aRef) => (
  <InDockBase {...aProps} RefForw={aRef} />
));

The forwarding prop can then be assigned to ref in the underlying class component:

class InDockBase extends React.Component {
  render() {
    return (
      <input name={"InDock" + this.props.ID} ref={this.props.RefForw} />
    )
  }
}

Lifecycle methods

Various lifecycle methods are invoked on class components as they are added to the page, updated, and later removed. Implementing these methods allows the component to perform special handling during these events.

Component mounting

A component is said to be mounted after it is first added to the page. The following methods are invoked during and after mounting:

  • The component constructor;
  • The static getDerivedStateFromProps method, which allows components to modify their state in response to props changes;
  • The render method;
  • The componentDidMount method. Side effects are allowed within this method.

Component updates

The following methods are invoked during the update phase, in response to props or state changes:

  • The static getDerivedStateFromProps method;
  • The shouldComponentUpdate method, which allows a component to bypass an update if the props or state change that triggered it does not affect its output;
  • The render method;
  • The getSnapshotBeforeUpdate method, which is invoked before changes are reflected in the DOM, allowing components to collect information about the previous DOM state;
  • The componentDidUpdate method. Side effects are allowed within this method.

Note that props changes cause the component to be re-rendered, but do not cause its class to be reinstantiated.

Component unmounting

A component is said to be unmounted after it is removed from the page. One method is invoked when unmounting:

  • The componentWillUnmount method. Side effects are allowed within this method.

Higher-order components

A higher-order component or HOC is a function that receives a component as an argument, and returns a new component:

// Returns a new component that renders component 'Child',
// with the result of function 'auSrcData' assigned to that
// component's 'Data' prop. Invoke method 'uUpd' in the new
// component to fetch new 'Data' and update:
function ConsumData(Child, auSrcData) {
  return class extends React.Component {
    constructor(aProps) {
      super(aProps);
      this.state = { Data: auSrcData() };
    }

    uUpd() {
      this.setState(aState => ({ Data: auSrcData() }));
    }

    render() {
      return <Child Data={this.state.Data} {...this.props} />;
    }
  };
}

This can be used to compose functionality. The child component is passed to the HOC, along with any other arguments it might need. The HOC can share state data with its child by assigning a prop. Other props are forwarded to the child with the props spread syntax.

The HOC result is stored in a variable:

function BoxLot(aProps) {
  return <div>...</div>
}

function uDataLot() {
  return { ... }
}

const Lot = ConsumData(BoxLot, uDataLot);

and then used like any other component:

<div>
  <Lot />
  ...

If the HOC were invoked within another component’s render method, a new class would be created with each update, and performance would suffer.

It is particularly easy to define an HOC if the containing component has already been implemented with a render prop:

class BridgeData extends React.Component {
  constructor(aProps) {
    super(aProps);
    this.state = { Data: aProps.uSrcData() };
  }

  uUpd() {
    this.setState(aState => ({ Data: this.props.uSrcData() }));
  }

  render() {
    return this.props.render(this.state.Data);
  }
}

The HOC simply returns a new function component that embeds the child within the forwarded render function:

function ConsumData(Child, auSrcData) {
  return aProps => (
    <BridgeData uSrcData={auSrcData} render={
      aData => (<Child Data={aData} {...aProps} />)
    } />
  );
}

Whereas the containing component in the first example passes data to the child by embedding it as a prop within its JSX, the container in the second passes it as an argument to the supplied render prop function, which embeds it within its own JSX.

Note that ref assignments look like props, but they are handled specially by React. If an ordinary ref is assigned to an HOC, it will come to reference the HOC itself, not the child component. If necessary, the HOC can use React.forwardRef to forward the ref to its child.

Performance optimization

As will be seen, ReactDOM.render is used to embed React content within one or more container elements in the page. For each container, React maintains an abstract representation of the content called the virtual DOM or VDOM. During the render phase, component render functions are invoked to produce a new virtual DOM tree, which React compares against the existing virtual DOM. The following functions may be called one or more times during the render phase, so they should not produce side effects:

  • Component constructors;
  • getDerivedStateFromProps;
  • State update functions passed to setState;
  • shouldComponentUpdate;
  • Component render functions.

During the commit phase, React updates the browser DOM to reflect any changes it detected, then it stores the new virtual DOM. The process as a whole is called reconciliation.

By default, a component is re-rendered if its props or state change. Re-rendering a parent also causes its children to be re-rendered, even if their props and state are unchanged. To improve performance, class components can override the shouldComponentUpdate method and return false if neither they nor their children should re-render for a given update. Class components can also subclass React.PureComponent, in place of React.Component. Before re-rendering, this class automatically compares values within the props and state to determine whether their content actually changed. Arrays and other objects are compared by reference, so only top-level values are considered. Function components can be wrapped with the React.memo HOC, which performs a similar props comparison before reinvoking the component function:

function Btn(aProps) {
  ...
}
export default React.memo(Btn);

Forms

Controlled components

Once displayed in the page, most elements change their appearance and behavior only in response to DOM operations. In React, this entails a change to the component’s props or state, which causes it to be re-rendered, and the DOM to be updated.

Form inputs respond to DOM operations, but they also respond directly to user input, even though their props and state have not changed. To make their state management consistent with other React elements, form inputs can be implemented as controlled components, which are explicitly backed by React state data. In this context, the word component may refer to a React component, or to a DOM element.

Within render, each controlled component reads its value from the form state. It also assigns an onChange handler that updates the form state to match new, user-entered values. It is this update — rather than the user’s input — that causes the new value to persist within the control. This is called one-way data binding:

class FormAddLot extends React.Component {
  constructor(aProps) {
    super(aProps);

    this.state = {
      IDLot: "000",
      CkBypass: false
    };

    this.uHandChange = this.uHandChange.bind(this);
    this.uHandSubmit = this.uHandSubmit.bind(this);
  }

  uHandChange(aEvt) {
    const oEl = aEvt.target;
    const oVal = (oEl.type === "checkbox") ? oEl.checked : oEl.value;
    const oState = { [aEvt.target.name]: oVal };
    this.setState(oState);
  }

  uHandSubmit(aEvt) {
    aEvt.preventDefault();

    const oDataLot = {
      IDLot: this.state.IDLot,
      Mode: this.state.Mode,
      CkBypass: this.state.CkBypass
    };
    ...
  }

  render() {
    return (
      <form onSubmit={this.uHandSubmit}>
        <div><label>Lot ID
          <input name="IDLot" value={this.state.IDLot}
            onChange={this.uHandChange} />
        </label></div>

        <div><label>Mode
          <select name="Mode" value={this.state.Mode}
            onChange={this.uHandChange}>
            <option value="Ready">Ready</option>
            <option value="Stand">Stand</option>
          </select>
        </label></div>

        <div><label>Bypass
          <input name="CkBypass" type="checkbox"
            checked={this.state.CkBypass}
            onChange={this.uHandChange} />
        </label></div>

        <input type="submit" value="Add" />
      </form>
    );
  }
}

If the onChange handler were omitted, the form state would go unchanged, and the user’s input would be overwritten with the default value, making the control effectively read-only. Controls set to null or undefined values in render are always editable, however.

Note that most input values are set with the value attribute in JSX, including elements like select and textarea, which specify values differently in HTML. Assigning an array to value selects multiple options, in controls that support multi-select. Checkbox values are set with checked.

Also, in HTML, an input is associated with a label by defining it as a child element, or by referencing it with the for label attribute. In JSX, for is replaced by htmlFor.

Uncontrolled components

Uncontrolled components are not backed by React state data; instead, React refs are used to reference DOM elements within the form, and the DOM API is used to read their values:

class FormCatAdd extends React.Component {
  constructor(aProps) {
    super(aProps);

    this.RefInCd = React.createRef();
    this.RefInName = React.createRef();
    this.uHandSubmit = this.uHandSubmit.bind(this);
  }

  uHandSubmit(aEvt) {
    aEvt.preventDefault();

    const oDataCat = {
      Cd: this.RefInCd.current?.value,
      Name: this.RefInName.current?.value
    };
    ...
  }

  render() {
    return (
      <form onSubmit={this.uHandSubmit}>
        <div><label>Code:
          <input type="text" defaultValue="A0" ref={this.RefInCd} />
        </label></div>

        <div><label>Name:
          <input type="text" ref={this.RefInName} />
        </label></div>

        <input type="submit" value="Add" />
      </form>
    );
  }
}

In React, the defaultValue attribute can be used to display a default value when the form appears, without overwriting the user’s input, as value does. Checkbox and radio button defaults can be set with defaultChecked.

File inputs do not allow their values to be set with the DOM API, so they must be implemented as uncontrolled components.

Page output

React.createElement

All React output is ultimately produced by React.createElement. JSX in particular is translated at compile time to nested invocations of this method:

React.createElement(type, [props], [...children])
Returns an immutable ReactElement instance. The content is defined by type, which references a React component, or a string that contains an HTML tag name. props can be set to an object that specifies the props for the new element, or null or undefined if no props are defined. One or more ReactElement instances can be passed as children, or a single array of these can be passed. The children instances become children of the new element.

When children references an array, each child must define a unique key value:

const oElA = React.createElement("div", { key: "A" }, "Area A");
const oElB = React.createElement("div", { key: "B" }, "Area B");
return React.createElement(React.Fragment, {}, [oElA, oElB]);

This is not necessary when children are passed as separate arguments:

const oElA = React.createElement("div", {}, "Area A");
const oElB = React.createElement("div", {}, "Area B");
return React.createElement(React.Fragment, {}, oElA, oElB);

ReactDOM.render

React output is typically embedded in the page with ReactDOM.render:

ReactDOM.render(elem, contain, [call])
Causes ReactElement instance elem and its children to replace the content of DOM element contain. If function call is provided, it will be invoked after contain is updated.

Though they can define any number of separate container elements, pages often have a single root element:

<div id="Root"></div>

that contains the entire React app:

const oElApp = (
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

ReactDOM.render(
  oElApp,
  document.getElementById("Root")
);

A root-level React component can be removed from the page by calling ReactDOM.unmountComponentAtNode, which accepts a single argument that gives the DOM element that contains the component.

Portals

Ordinarily, a component’s output is written to its position in the element hierarchy. Any elements it generates become children of the component’s parent, and siblings of the component’s siblings.

A portal can be used to direct output to a DOM element other than the component’s parent. The component invokes ReactDOM.createPortal within its render function, and returns the resulting portal instance. This method is invoked much like ReactDOM.render:

ReactDOM.createPortal(elem, contain)
Creates and returns a portal object that causes ReactElement elem to be rendered into DOM element contain, rather than the parent of the invoking component. elem and its children replace the content of contain.

By invoking ReactDOM.createPortal conditionally, a component can send its output to a portal, or to the component’s parent, as usual:

const ElDlg = document.getElementById("Dlg");

function SecMsg(aProps) {
  const oOut = (
    <section>
      <h3>{aProps.Head}</h3>
      <div>
        {aProps.Msg}
      </div>
    </section>
  );

  if (aProps.CkDlg) return ReactDOM.createPortal(oOut, ElDlg);
  return oOut;
}

Many DOM events bubble up through the element hierarchy until stopPropagation is invoked on the event. React propagates its own SyntheticEvent instances through the React element tree, and these events continue to move through the hierarchy that contains the portal-using control, even though the control’s output has been redirected to a different hierarchy within the DOM.

Error boundaries

By default, when a component throws an exception from its constructor, its render method, or a lifecycle method, the component and its content are removed from the page.

An error boundary is a special component that catches these exceptions when they are thrown from contained components, allowing errors to be logged, and fallback content to be displayed. A class component will act as an error boundary if it defines one or both of getDerivedStateFromError and componentDidCatch. Function components cannot serve as boundaries.

getDerivedStateFromError is a class-static method that is called by React when a contained component throws. It receives the exception as a parameter, and returns an object that React uses to update the component state. Often, this state object is used to set a property that the render method uses to display fallback content:

static getDerivedStateFromError(aErr) {
  return { CkErr: true };
}

componentDidCatch is a component class method that is called by React after getDerivedStateFromError. It receives the exception as an argument, plus an object containing a componentStack property that stores a stack trace:

componentDidCatch(aErr, aInfoErr) {
  Log.Add(aErr, aInfoErr);
}

Note that development builds display exception text and stack traces in the page even if those exceptions were caught by error boundaries. To see the page as it would be rendered by a production build, press Esc.

Error boundaries do not catch exceptions thrown from DOM event handlers or asynchronous functions. Event handler exceptions do not cause the component’s content to be removed from the page, however.

Hooks

Function components produce no component instances, so they are traditionally unable to maintain component state, or use stateful features like refs. React 16.8 introduced hooks, which are special functions that add these abilities to function components.

Several rules apply to hook usage. CRA installs an ESLint plugin eslint-plugin-react-hooks that checks some of these at compile time:

  • Hooks are usable only within function components, or within custom hook functions. They cannot be used in class components;
  • Hooks that are called once must be called every time the component is rendered, and in the same order. For this reason, they may not be called conditionally, or from event handlers, nor may the containing function place a conditional early return before any hook. eslint-plugin-react-hooks also prevents hooks from being called within loops, even when the iteration count is fixed. It is also recommended that hooks not be called within nested functions.

When a hook is invoked from a function component, it creates or modifies state data that is specific to the component instance. When the component instance is rendered again, the same hook is made to reference the same data.

State hooks

State hooks are created with the useState function within the react package. They allow function components to manage state, much the way class components do with setState:

import React, { useState } from "react";

Each useState call defines a single state variable, which can store a primitive or an object:

function Spin(aProps) {
  const [oVal, ouSet_Val] = useState(aProps.ValDef | 0);

  function ouInc(aEvt) { ouSet_Val(aVal => (aVal + 1)); }
  function ouDec(aEvt) { ouSet_Val(aVal => (aVal - 1)); }

  return (
    <div>
      <div>{oVal}</div>
      <button onClick={ouDec}>-1</button>
      <button onClick={ouInc}>+1</button>
    </div>
  );
}

useState accepts a starting value for the variable, or a function that returns such a value. If a function is passed, it is invoked only once, when useState is first called. This can be used to avoid lengthy operations that would otherwise be repeated every time the containing function component re-renders.

useState returns an array containing the current value, plus a function that can be used to update the value. This update function completely replaces state objects; it does not merge them as setState does. It can accept a new value, or a callback that accepts the current value and returns the new one. To avoid race conditions, the callback should be used if the new value derives from the old.

Calling useState more than once produces multiple state variables; React uses the order of these calls to link each with its particular allocation. It is recommended that values or objects that vary independently be managed with separate useState calls, rather than combining them into a single object.

When the component is selected in the React DevTools Components tab, its hooks and their associated data are listed in the hooks section within the component properties.

Reducer hooks

Reducer hooks can be used to manage state in components with complex state transitions.

A reducer is a pure function that accepts an object representing the current state, plus a second action argument that represents a state transition. The reducer uses these to return a new object representing the next state.

useReducer can be called with two arguments:

  1. A reducer;
  2. An object that defines the initial state of the component.
or three:
  1. A reducer;
  2. Any value;
  3. A function that accepts the second argument, and returns the initial state.

useReducer returns an array containing the current state object, plus a dispatcher. This function accepts an action argument and forwards it to the reducer, which triggers the next state transition:

import React, { useReducer } from "react";

function uStSpinNext(aSt, aAct) {
  switch (aAct.Type) {
    case "DEC":
      return { Val: aSt.Val - aAct.Qty }
    case "INC":
      return { Val: aSt.Val + aAct.Qty }
    default:
      throw Error("uStSpinNext: Invalid action type");
  }
}

function Spin(aProps) {
  const oStInit = { Val: aProps.ValDef | 0 };
  const [oSt, ouDispatSt] = useReducer(uStSpinNext, oStInit);

  function ouDec1(aEvt) { ouDispatSt({ Type: "DEC", Qty: 1 }); }
  function ouDec10(aEvt) { ouDispatSt({ Type: "DEC", Qty: 10 }); }

  function ouInc1(aEvt) { ouDispatSt({ Type: "INC", Qty: 1 }); }
  function ouInc10(aEvt) { ouDispatSt({ Type: "INC", Qty: 10 }); }

  return (
    <div>
      <div>{oSt.Val}</div>
      <button onClick={ouDec10}>-10</button>
      <button onClick={ouDec1}>-1</button>
      <button onClick={ouInc1}>+1</button>
      <button onClick={ouInc10}>+10</button>
    </div>
  );
}

useReducer returns the same dispatcher instance each time, so the dispatcher can be assigned as a prop without producing unnecessary updates. This allows child components to trigger state transitions.

Effect hooks

Like all render functions, function components are prohibited from producing side effects, at least directly. Side effects include DOM changes, network requests, and even logging.

Effect hooks provide a way to run code that produces side effects from function components, serving much as componentDidMount, componentDidUpdate, and componentWillUnmount do in class components.

useEffect accepts an effect function. React will call this function some time after the component has been rendered, and the browser repainted, so it can produce side effects:

import React, { useState, useEffect } from "react";

function StatPress(aProps) {
  const [oData, ouSet_Data] = useState(null);

  // Subscribes this component to the status data publisher,
  // causing pressure data to be forwarded to 'ouSet_Data',
  // then unsubscribes it:
  function ouEffSubData() {
    const ouSub = aData => ouSet_Data(aData);
    PubDataStat.Add_Sub(CdStatPress, ouSub);
    return () => PubDataStat.Rem_Sub(CdStatPress, ouSub);
  }
  useEffect(ouEffSubData, []);

  if (!oData) return "PEND";

  return (
    <section>
      <h2>Pressure</h2>
      <div>Current: {oData.Curr}</div>
      ...
  );
}

The effect function accepts no parameters. It can return a zero-parameter cleanup function; if it does, React will call this function before the effect function is called again, and also before the component is unmounted.

By default, React calls the effect after every render, and the cleanup before every effect after the first. However, useEffect accepts an optional array of state variables or other dependencies by referenceas its second argument. If provided, React stores these values and compares them after each render; neither the cleanup function nor the effect are invoked unless one or more of the values change, with objects being compared by reference. If an empty array is passed, both functions run exactly once: the effect after the first render, and the cleanup before the component unmounts.

If the effect function uses state variables, directly or indirectly, omitting these from the array may cause the effect to run with obsolete data. For instance, if the effect assigns a handler to the DOM, and if that handler uses a state variable, the used variable must be registered as a dependency. Otherwise, the first handler instance — which recorded the state when useEffect was first called — will persist, even after the state has changed:

  const [oCkPause, ouSet_CkPause] = useState(false);

  // Adds a 'keydown' listener to the page, then removes it:
  function ouEffRegHandKeyDown() {
    function ouHand(aEvt) {
      if ((aEvt.code === "Escape") && oCkPause) ...
    }
    document.addEventListener("keydown", ouHand);
    return () => { document.removeEventListener("keydown", ouHand); }
  }
  useEffect(ouEffRegHandKeyDown, [oCkPause]);

For this reason, ESLint warns that React Hook useEffect has a missing dependency when used variables are omitted.

Like useState, useEffect can be called more than once in a given component.

Layout effect hooks

Ordinary effect hooks run after the browser has painted the changed component. Sometimes it is necessary to update the DOM manually after rendering, and it is preferable to do this before painting. That can be done with useLayoutEffect, which is identical to useEffect, except that its effect and cleanup functions run before the browser is allowed to paint.

Context hooks

In the past, function components used context values by assigning a render prop to the context object’s Consumer component. Context hooks provide a simpler way to read those values.

The context object is created with React.createContext, as before, and its value is assigned the same way, by embedding the object’s Provider component and setting the associated value prop. However, the function component can now read the value simply by passing the context object to useContext:

function Box(aProps) {
  const oStat = useContext(ContextStat);
  return <div className={`Box ${oStat}`}>PEND</div>
}

Ref hooks

Class components have always used React refs to reference component or DOM element instances that they rendered. It was not originally possible for function components to do this, but they can now by using ref hooks. These refs are more general than the element refs produced by React.createRef; they can be used to persist any sort of data, for use by event handlers, or future iterations of the calling function.

These ref objects are created with useRef. Unlike React.createRef, this function accepts a single argument that determines the starting value of the ref’s current property. Much like useState, the same object is returned every time a particular useRef line is executed.

The current property is made to reference a component or DOM element instance by assigning it to the target’s ref attribute. As usual, this value is not set until the first time the component is rendered:

function PanLog(aProps) {
  const oRefBtn = useRef(null);

  function uReady() {
    oRefBtn.current?.focus();
    ...
  }
  useEffect(uReady, []);

  return (
    <div>
      <button ref={oRefBtn}>Reset</button>
      ...
  );
}

The current property can also be set manually, to any value, as can any other property in the ref object. The result is something like a static local variable in another language. Unlike component props or state, changing such a value does not cause the component to be re-rendered.

Customizing element ref output

By default, when a ref is assigned to an element’s ref attribute, its current property is made to reference that component or DOM element instance. useImperativeHandle can be used to assign a different value to current.

As always when forwarding refs, the target component is implemented as a React.forwardRef callback, with props and ref arguments. If it needs to reference one of its own children, it creates its own ref and assigns that as usual; it does not use the ref it received as a parameter, as that will be read by the target’s parent. The component passes the ref it received to useImperativeHandle, along with a callback that accepts no arguments, and returns the value or object that should be assigned to current in the customized ref:

function PanPowBase(aProps, aRef) {
  const oRefBtn = useRef();
  function ouReady() {
    oRefBtn.current?.focus();
    ...
  }
  // Assign an object containing the 'Ready' function to
  // the parent's ref:
  useImperativeHandle(aRef, () => ({ Ready: ouReady }));

  return (
    <>
      <div>
        Power
        <button ref={oRefBtn} onClick={...}>Run</button>
      </div>
      ...
    </>
  );
}

React.forwardRef produces the finished component:

const PanPow = React.forwardRef(PanPowBase);

The parent assigns a ref to the target as usual, and obtains the target’s data from the ref object’s current property:

function PanMain(aProps) {
  const oRefPanPow = useRef(null);
  const ouReady = () => oRefPanPow.current?.uReady();

  return (
    <div>
      <PanPow ref={oRefPanPow} />
      <button onClick={oReady}>Ready</button>
    </div>
  );
}

Memoization hooks

Memoization is the caching and reuse of one function’s output by another function. Memoization hooks provide an easy way to memoize slow operations within function components.

useMemo accepts the target function, plus an array of state variables. It does not pass arguments when it invokes the target, so an arrow function may be used to capture and forward props or state variables:

function PanFilt(aProps) {
  const [oFreq, ouSet_Freq] = useState(800);
  const [oWth, ouSet_Wth] = useState(1);

  const oCoeffs = useMemo(
    () => CoeffsFilt(oFreq, oWth),
    [oFreq, oWth]
  );
  ...

Much like useEffect, values in the state array are stored and compared each time the hook is invoked; the target function is evaluated the first time the hook runs, and again if any state value changes. useMemo caches only the most recent target result. The array elements typically match the state variables that were captured by the target function, and eslint-plugin-react-hooks warns if any of these are omitted in the array. If the array is empty, the target will be invoked only once. If the array argument is omitted, the target will be invoked every time, bypassing the memoization entirely.

Callback memoization

Sometimes it is desirable to pass a function to a child element through one of its props, for use as an event handler. However, if this handler is defined in a function component, a new function instance will be created during each render. The resulting props change will cause the child to re-render every time the parent renders, even if the child uses PureComponent or React.memo.

This problem can be avoided with useCallback, a hook that memoizes function instances, rather than function results. Like useMemo, it accepts a target function and an array of state variables. Rather than invoking the target, however, it returns a new function that wraps the target, and it continues to return the same wrapper instance until one or more of the state values change:

const ouCkEnab = useCallback(
  function (aPos) {
    return !oEntUser || EntWord.uCkTogAt(oEntUser, aPos);
  },
  [ oEntUser ]
);

Note that the same could easily be done with useMemo, since:

const ouHand = useCallback(func, vars);

is equivalent to:

const ouHand = useMemo(() => func, vars);

Custom hooks

A custom hook is a function that calls other hooks, allowing related state management functionality to be collected and reused in different components. The custom hook’s name must begin with use, but the function itself can have any signature. This allows the hook to gather any sort of data, perform stateful work, and then return any sort of result, for use during the component render.

The rules that govern built-in hooks also apply to custom hooks. In particular, custom hooks that are called once must be called every time, and in the same order.

Debug string hooks

When a component is selected in the React DevTools Components tab, its hooks and their associated data are listed in the hooks section within the component properties. Custom hooks are also listed there. If a string is passed to useDebugValue, that value will be displayed in a label next to the custom hook name.

Sources

React documentation
Add React to a Website, Building Your Own Hooks, Context, Create a New React App, DOM Elements, Error Boundaries, Forms, Forwarding Refs Higher-Order Components, Hooks at a Glance, Hooks API Reference, Hooks FAQ, Introducing Hooks, JSX In Depth, Portals, React Top-Level API, React.Component, ReactDOM, Reconciliation, Refs and the DOM, Rules of Hooks, Strict Mode, SyntheticEvent, Typechecking With PropTypes, Uncontrolled Components, Using the Effect Hook, Using the State Hook, Virtual DOM and Internals
Retrieved December 2021

Create React App documentation
Adding Images, Fonts, and Files, Adding a Stylesheet, Advanced Configuration, Code Splitting, Folder Structure, Using the Public Folder
Retrieved December 2021

Create React App GitHub page
Retrieved December 2021

Stack Overflow
How to use React.forwardRef in a class based component?, What does the callback in forceUpdate do?, What is JavaScript's CompositionEvent?
Retrieved December 2021

React (Virtual) DOM Terminology
Sebastian Markbåge
Retrieved December 2021

web.dev
Add a web app manifest
Retrieved January 2022

Webpack documentation
Asset Management
Retrieved January 2022