React Notes
Compiled by Jeremy Kelly
www.anthemion.org
Printed UNKNOWN
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
- Creating a project
- Running the development build
- Running tests
- Deploying the project
- Development with CRA
- Project structure
- Importing modules
- Code-splitting
- Importing CSS
- Importing other files
- JSX
- Embedded expressions
- Event handling
- React event handlers
- Components
- Function components
- Class components
- Component props
- Default props
- Prop type checking
- Component state
- Component children
- Render props
- Fragments
- Component context
- Element refs
- Callback refs
- Forwarded refs
- Lifecycle methods
- Component mounting
- Component updates
- Component unmounting
- Higher-order components
- Performance optimization
- Forms
- Controlled components
- Uncontrolled components
- Page output
- React.createElement
- ReactDOM.render
- Portals
- Error boundaries
- Hooks
- State hooks
- Reducer hooks
- Effect hooks
- Layout effect hooks
- Context hooks
- Ref hooks
- Customizing element ref output
- Memoization hooks
- Callback memoization
- Custom hooks
- Debug string hooks
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.
Sources
Create a New React AppRunning the development build
To start the development server and run the development build:
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"
Note that 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 theindex.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.
Sources
Folder Structure Using the Public Folder Advanced Configuration Add a web app manifestImporting 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 .mjs
, 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>
);
Sources
Code SplittingImporting 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
.
Sources
Adding a Stylesheet Asset ManagementImporting 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.
Sources
Adding Images, Fonts, and FilesJSX
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>
);
Some developers use the jsx
file extension for JavaScript files that contain even small amounts of JSX. This does not change how the files are processed.
Sources
DOM ElementsEmbedded 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, inside the string:
<button onclick="uHandReady()">Ready</button>
In JSX, the handler is not invoked; 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 this type of 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>
Sources
SyntheticEvent What is JavaScript's CompositionEvent?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
Form events
Text input selection events
Mouse events
Touch events
Pointer events
Keyboard events
Clipboard events
CSS transition events
CSS animation events
Media events
Other events
Sources
JSX In DepthComponents
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 specified 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.
Sources
React.ComponentComponent 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 and value checking on component props. Type checks are defined by assigning an object to the component’s propTypes
property. For each check that fails, a warning will be logged to the console. Checks are performed only during the development build:
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
};
The propTypes
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 any reference to a React component.
instanceOf(class)
- Matches an object that inherits, directly or indirectly, from class.
-
oneOf(vals)
- Matches any value 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, aNameCpt) {
if (aProps.NumShelf
&& ((aProps.NumShelf < 1) || (aProps.NumShelf > NumShelfMax)))
return new Error(aNameCpt + ": 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.
Sources
Typechecking With PropTypesComponent 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.
Sources
What does the callback in forceUpdate do?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 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. The prop can have any name, but it is conventional to call it render
:
<Spin render={
aVal => (<div>Current: {aVal}</div>)
}/>
Note that defining the prop function within JSX causes a new function instance to be created each time the JSX is evaluated. That could affect performance, and it should not be combined with React.PureComponent
, which 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, 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>
);
}
}
Sources
ContextElement 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 InpName extends React.Component {
constructor(aProps) {
super(aProps);
this.RefInp = React.createRef();
this.uFocus_Inp = this.uFocus_Inp.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.RefInp} />;
}
The component or DOM element instance is then accessible through the current
property in the ref object:
uFocus_Inp() {
this.RefInp.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_ElInp} />
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_ElInp(aEl) {
this.ElInp = aEl;
}
If the callback is a class method, it should be bound to this
, like other event handlers:
constructor(aProps) {
super(aProps);
this.uSet_ElInp = this.uSet_ElInp.bind(this);
this.uFocus_Inp = this.uFocus_Inp.bind(this);
}
The reference itself is passed to the callback, so there is no current
property:
uFocus_Inp() {
this.ElInp?.focus();
}
Forwarded refs
A parent component cannot associate a ref
attribute with 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 InpDock = React.forwardRef((aProps, aRef) => (
<input name={"InpDock" + aProps.ID} ref={aRef} />
));
class BoxDoc extends React.Component {
constructor(aProps) {
super(aProps);
this.RefInp = React.createRef();
this.uReady = this.uReady.bind(this);
}
uReady() {
this.RefInp.current?.focus();
...
}
render(aProps) {
return (
<div>
<InpDock ref={this.RefInp} />
...
</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 InpDock = React.forwardRef((aProps, aRef) => (
<InpDockBase {...aProps} RefForw={aRef} />
));
The forwarding prop can then be assigned to ref
in the underlying class component:
class InpDockBase extends React.Component {
render() {
return (
<input name={"InpDock" + this.props.ID} ref={this.props.RefForw} />
)
}
}
Sources
Refs and the DOM Forwarding Refs How to use React.forwardRef in a class based component?,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.
Sources
Higher-Order ComponentsPerformance 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);
Sources
Reconciliation Virtual DOM and Internals React (Virtual) DOM TerminologyForms
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.RefInpCd = React.createRef();
this.RefInpName = React.createRef();
this.uHandSubmit = this.uHandSubmit.bind(this);
}
uHandSubmit(aEvt) {
aEvt.preventDefault();
const oDataCat = {
Cd: this.RefInpCd.current?.value,
Name: this.RefInpName.current?.value
};
...
}
render() {
return (
<form onSubmit={this.uHandSubmit}>
<div><label>Code:
<input type="text" defaultValue="A0" ref={this.RefInpCd} />
</label></div>
<div><label>Name:
<input type="text" ref={this.RefInpName} />
</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.
Sources
Forms Uncontrolled ComponentsPage 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, ornull
orundefined
if no props are defined. One or moreReactElement
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);
Sources
React Top-Level APIReactDOM.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.
Sources
Add React to a Website ReactDOM Strict ModePortals
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.
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.
Sources
PortalsError 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.
Sources
Error BoundariesHooks
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 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.
Sources
Introducing Hooks Hooks at a Glance Rules of Hooks Hooks API Reference Hooks FAQState 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.
Sources
Using the State HookReducer 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:
- A reducer;
- An object that defines the initial state of the component.
- A reducer;
- Any value;
- 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'.
// Returns a function that 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. Returns a function
// that removes the listener:
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.
Sources
Using the Effect HookLayout 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 do so now by using ref hooks. These refs are more general than the element refs produced by React.createRef
; they can 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. As with 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. 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={ouReady}>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.
Sources
Building Your Own HooksDebug 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.