React notes

Some concepts I don’t know from backend programming languages, some places I might make mistakes.

React: >= v16.13.0

When to use

React is for developing big client-side web applications.

React is a library that keeps the DOM in sync with your data. It is not equal to Web Components, they solve different problems.

Kinds of web applications

  • Round-Trip
    Clicking a link or button leads the browser to load a new page, the browser shows HTML pages, most of application logics and data are resident on the server.
  • Single-Page
    SPA(Single-Page Applications) is an another approach. The browser loads an initial HTML page, the page will not be reloaded or replaced later, user interactions lead to HTTP requests for parts of the page or data, then update the existing HMTL elements.

React is well-suited to SPAs.

Other solutions

React is a library that is the ‘V’ of MVC.

  • Angular
    A framework. You need to walk along the direction that it specified.

  • Vue.js
    It is between Angular and React.

JSX

  • true, false, null, undefined are ignored;

    <div>{true}</div>

    This code is valid, but doesn’t render anything.

    <div>
        {showHeader && <Header />}
    </div>    

    JSX renders the <Header /> component only if showHeader is true.

    But

    <div>
        {props.messages.length && <MessageList messages={props.messages/>}
    </div>    

    JSX still renders the MessageList component if the length is 0. Need to use the condition props.messages.length > 0.

  • Always start component names with a capital letter, otherwise React treats the components as DOM tags.

  • React requires components to return a single top-level element. Use the following special React element to return multiple elements.

    return <React.Fragment>
           {/* ... */}
           </React.Fragment>
    
    // OR
    
    return <>
           {/* ... */}
           </>       
  • Multiple line JSX statements should be enclosed by parentheses, it isn’t required, but should be, to avoid the pitfalls of JavaScript’s automatic semicolon insertion.

    // "element" is a React component
    const element = <h1>Hello, {name}</h1>;
    
    // OR 
    
    const element = (
        <h1>
            Hello, {name}
        </h1>
    );

CSS

The methods to style:

  • Plain CSS stylesheet (as usual), just import the CSS files and use the class(className in React) attribute on HTML elements. Could use BEM (Block Element Modifier) to solve the global namespace issue and use other existing libraries and tools, such as Sass, Tailwind CSS;
  • CSS modules. It works with webpack or Browserify and is not an official spec. Create a button.module.css or button.module.scss, only import it into button.js, the rules only apply to the components within button.js;
  • Web Components. The CSS styles of the components are local scoped;
  • CSS-in-JS, like the styled-components third-party library. This method unlocks some features, such as theme nesting, dynamic styles;
  • Inline styling;

Sass and styled-components

Sometimes we use a CSS framework and also want to use styled-components to customize it. The example created by create-react-app uses bulma:

  • Install node-sass, bulma, styled-components;

  • Import the bulma button element;

    button.scss:

    @import "bulma/sass/utilities/_all.sass";
    @import "bulma/sass/elements/button.sass";
  • App.js

    import React from 'react';
    // import styled from 'styled-components/macro';
    import styled from 'styled-components';
    import "./button.scss"
    import './App.css';
    
    function Button(props) {
        // => className = 'sc-AxjCm fzAihE'
        const {className, ...rest} = {...props};
        
        // => <button class="button sc-AxjCm fzAihE">Click</button>
        return <button className={`button ${className}`} {...rest}>Click</button>
    }
    
    const StyledButton = styled(Button)`
        color: red;
        `;
    
    function App() {
        // return <Button css="color: red"/>;  // another way to style
        return <StyledButton />;
    }
    
    export default App;

    CSS modules could work together with Sass and styled-components, but it treats all class names from the two common files and customized class names within button.module.scss(renamed button.css to button.module.css) as local class names. This is not good, it is very hard to avoid using common CSS files completely in practice. Exported common class names like this:

    exports.locals = {
        "button": "button_button__13ZnZ",
        "is-loading": "button_is-loading__3N-29",
        // ...
    }

    Web Components also could isolate CSS styles via the Shadow DOM. No random generated class names, more natural.

Props

  • The value of React Props can be a literal string or an expression(in {}).

    <button text="Click" disabled={false}>
  • React components could pass data props or function props to its child components. If the callback functions have arguments, the expression includes parentheses that leads a function call, must put them into a function.

    <button onClick={ () => props.promoteCallback(props.name) }>
        {/* ... */}
    </button>

    Not like this:

    // React will invoke the function when the component renders its content, not when the user clicks the button.
    <button onClick={ props.promoteCallback(props.name) }>
        {/* ... */}
    </button>
  • Props could have type checking and default values. Be performed only during development.

    export function SimpleButton(props) {
        // ...
    }
    
    SimpleButton.defaultProps = {
        disabled: false
    }
    
    SimpleButton.propTypes = {
        text: PropTypes.string,
        // ...
        disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
    }
    
    // class-based components also support these features
  • Elements generated from arrays are required to have a key property, React is able to minimize the number of changes to display a change. Its value must be unique and stable.

State

  • Stateless and stateful components

    stateless

    export function Clock(props) {
        return (
            // ...
        );
    }

    stateful

    // Only one Clock instance for a same DOM node, but call the render method each time an update happens
    export class Clock extends React.Component {
        constructor(props) {
            super(props);
            this.state = {date: new Date()};
        }
        
        render() {
            return (
                // ...
            );
        }    
    }

    Also could use the useState Hook to implement a stateful function-based component.

  • Only modify state data through the setState function. React will batch individual updates to the same state data together for efficiency and the process is asynchronous. Pass a function as the first argument if having a series of dependent changes.

    setState(updater[, callback])

    The updater could be an object or a function;

    The callback is a function which will be executed once setState is completed and the component is re-rendered. Recommend using componentDidUpdate() instead of it;

    // callback function
    this.setState({ counter: this.state.counter + 1 },
                () => this.setState({ buttonClicked: this.state.counter > 0 }));
                
    // the updater is a function for a same data property of the state
    for (let i = 0; i < 5; i++) {
        this.setState((state, props) => { return { counter: state.counter + 1 }});
    }

    More details here:
    https://reactjs.org/docs/react-component.html#setstate

Shallow copy

Props, setState returned objects, useState‘s updater function returned objects all are shallow-copied objects. Like this:

const obj = {x: {num: 1}};
const clonedObj = Object.assign({}, obj);
console.log(obj === clonedObj, obj.x === clonedObj.x);  // => false true

The x object is same and mutable, the cloned object is different every time. If all properties of an object are primitive values, it is simple.

Class-based components could use shouldComponentUpdate(nextProps, nextState) to do deep checks to prevent further updates as a performance optimization.

Function-based components could use React.memo to check for prop changes, no state checks.

const WrappedFnComponent = React.memo(FnComponent, areEqual);

// ...

// usage
<WrappedFnComponent />

However React doesn’t recommend doing deep checks.

function MyLabel(props) {
    // ...
}

// ...

<MyLabel msg = { anObject }/>

props.msg is read-only, but objects within it are mutable.

The following example would like to say don’t think the deferred operation, such as useEffect, already captured a props or state snapshot, especially when don’t know whether they contain mutable objects. The props and state objects aren’t immutable objects.

// App.js
import React from 'react';
import {useState, useEffect, useRef} from 'react';
import './App.css';

function Count(props) {
    const [number, setNumber] = useState(-1);
    let timer = useRef(0);

    useEffect(() => {
        // const savedNumber = props.count.obj.num;  // make a snapshot
        setTimeout(() => {
            timer.current += 1;
            console.log('timer', timer.current);
            
            // setNumber(savedNumber);
            
            // In a closure, use the final value of the variable
            setNumber(props.count.obj.num);
        }, 10000);
    }, [props]);

    return <p>{`Timer: ${timer.current}, count: ${number}`}</p>;
}

function MyButton() {
    const [count, setCount] = useState({obj: {num: 0}});

    const handleClick = () => {
        console.log('click', count.obj.num, '=>', count.obj.num + 1);
        count.obj.num += 1;
        // make sure the object is different, let React render the component again
        setCount(count => ({...count}));
    };

    return <>
        <Count count={count} />
        <button onClick={handleClick}>Click</button>
        </>;
}

function App() {
    return <MyButton />;
}

export default App;

Quickly click several times within 10 seconds, would see the span element shows the final number of clicks, then keep it. Except the first timer(no click), other timers see and set a same state value, so don’t re-render the Count component. Saving the variable that will be used when firing an effect fixed the problem.

More details here:
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
https://reactjs.org/docs/react-api.html#reactmemo

Sharing data

Sharing data between react components:

  • Lift the shared state up to the closest common ancestor, then distribute the state as props;
  • context feature;
  • Data store, such as Redux;
  • URL routing, some state data encoded in the URL;

Events

  • Accessing component data in an event handing method.

    export default class App extends Component {
        
        // ...
        
        handleEvent() {
            this.setState({ message: "Clicked!"});
        }
        
        render() {
            return (
                // ...
                
                // don't work, "this" is undefined when calling the event handler
                <button onClick={ this.handleEvent } />
                
                // ...
            );
        }
    }

    Three alternative ways to fix the problem:

    // To change handleEvent, the arrow function finds the "this" from its enclosing scope(automatic binding).
    handleEvent = () => {
        this.setState({ message: "Clicked!"});
    }
    // To change onClick
    onClick={ () => this.handleEvent() }
    // To bind handleEvent
    constructor(props) {
        super(props);
        this.handleEvent = this.handleEvent.bind(this);
    }    
  • React doesn’t support all DOM events and doesn’t allow components to create and publish custom events.

  • React always provides a SyntheticEvent object for an event handler, but it will reuse SyntheticEvent objects and reset all the properties to null once an event has been handled, can’t use its properties within asynchronous operations.

    handleEvent = (event) => {
        event.persist();  // must add, otherwise event.type is null
        this.setState({ counter: this.state.counter + 1},
            () => this.setState({ message: `${event.type}: ${this.state.counter}`}));
    }
  • Event handing excludes the custom HTML elements that are used to apply components.

    // CustomButton is a React component that custom the <button> element
    // Won't invoke the handleClick, no errors
    <CustomButton onClick={ this.handleClick }/>
  • Event phase

    • Capture phase: The event is being propagated through the target’s ancestor objects, This process starts with the Window, and through descendent elements until the target’s parent is reached. Would invoke the event handles that are added by addEventListener() with useCapture option. In React, use the onClickCapture property of an element to specify the event handler.
    • Bubble phase: The event is being propagated in reverse order of the capture phase, staring with the parent and eventually reaching the Window. Once the event is handled by the target element, it enters the bubble phase.

    More details here:
    https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase

Component lifecycle

Class-based components:

  • Mount phase: constructor() -> getDerivedStateFromProps() -> render() -> componentDidMount()
  • Update phase: getDerivedStateFromProps() -> shouldComponentUpdate() -> render() -> getSnapshotBeforeUpdate() -> componentDidUpdate()
  • Unmount phase: componentWillUnmount()

React Lifecycles

Initializing a component and changing the state or props will call the render method.

When a component’s props or state change, React compares the newly returned element with the previous rendered one. When they are not equal, React will update the DOM. This process is called reconciliation. The process uses React’s diffing algorithm to minimize changes to the DOM on every update.

The main use of the componentDidUpdate method is to directly manipulate the HTML elements in the DOM using the React refs feature, the refs feature provides access to the HTML elements rendered by a component after they have been added to the DOM, the method is called even if the reconciliation process determines that the content generated by the component has not changed.

A component’s state can be passed to its descendant components as props, its state is changed, its descendant components are rendered as well. If the update is unnecessary, let its descendant components’ shouldComponentUpdate method return false, React won’t call these components’ render and componentDidUpdate methods.

React will trigger the component’s update phase when an ancestor’s state has changed, the getDerivedStateFromProps method may be called even though none of the prop values on which the component depends has changed.

Use the getSnapshotBeforeUpdate method to capture information from the DOM before it is potentially changed. Could be used in conjunction with the refs feature, such as to remember and restore the scroll position.

Changing the top-level element within a component will cause React to replace its descendant components without the reconciliation process. Don’t do it whenever possible.

Don’t make HTTP requests in the render method that is called often, move the requests to the componentDidMount method, but if the component would be mounted and unmounted frequently, lifting the requests up to a stable component to avoid unnecessary data requests. Once the data arrives in the componentDidMount method, it changes the state, the update phase comes into play, the render method is called again.

More details here:
https://reactjs.org/docs/react-component.html
https://github.com/wojtekmaj/react-lifecycle-methods-diagram

Function-based components:

  • Function-based components can’t participate in the component lifecycle in the same way like class-based components. The React.useEffect Hook is used to register a function that will be invoked when the component is mounted, updated, and unmounted, it is a three-in-one function.

Composition vs Inheritance

Composition instead of inheritance to reuse components.

  • Use the special children prop

    function FancyBorder(props) {
        return (
            // ...
            
            { props.children }
            
            // ...
        );
    }              
    function WelcomeDialog() {
        return (
            <FancyBorder>
            
            {/* ... */}
            
            </FancyBorder>
        );
    }

    The WelcomeDialog component reuses the FancyBorder component. The props.children will be replaced by the elements included by FancyBorder in the WelcomeDialog component.

    There are map, forEach, toArray, etc. methods in React.Children, could use them to get advanced knowledge about these children, with React.cloneElement to modify them, such as adding new props.

  • Pass components as props

    function App() {
        return (
            <SplitPane left = { <Contacts /> } right = { <Chat /> } />
        );
    }

    Contacts and Chat are components, and are also objects, could pass them as props.

  • A specific component renders a generic one

    function WelcomeDialog() {
        return <Dialog title='Welcome' message='Hi' />
    }

    The WelcomeDialog component configures a generic Dialog with props, making it specific.

Higher-order components (HOCs)

A HOC is a function that takes a component and returns a new component. It composes the original component by wrapping it in a container component.

Some tasks, like logging, data retrieval, have same logical process flow. HOCs abstract this logic in the container component and share it across many components, the wrapped component receives data that generated by this logic as props to do its own jobs.

function withSubscription(WrappedComponent, selectData) {  // takes a component
    return class extends React.Component {                 // return a component
        constructor(props) {
            super(props);
            
            // ...
            
            this.state = {
                data: selectData(DataSource, props)
            };
        }
        
        // ...
        
        render() {
            return <WrappedComponent data={this.state.data} {...this.props} />;
        }
    };
}

Put HOCs outside the component definition so that it is created only once, otherwise a new version of the component is returned and unmount/remount it each time.

const BlogPostWithSubscription = withSubscription(
    BlogPost,
    (DataSource, props) => DataSource.getBlogPost(props.id)
);

export default class App extends Component {
    // ...
}

The wrapped components or container components could be function-base components or class-based components, stateless components or stateful components.

The wrapped components could be wrapped many times.

Render props

This is a technique for sharing code between React components using a prop whose value is a function.

Alternative model of wrapping one component in another. It could implement most HOCs using a regular component with a render prop. It is simpler than HOCs.

class Mouse extends React.Component {
    constructor(props) {
        super(props);
        
        // ...
        
        this.state = { x: 0, y: 0 };
    }
    
    // ...
    
    render() {
        return (
            // ...
             
            {this.props.render(this.state)}
            
            // ...
        );
    }
}

class MouseTracker extends React.Component {
    render() {
        return (
            // ...
            
            /* The place of the render prop needs a function,  
             * could use other names rather than "render", but
             * make sure to change the name of the line 12
             */
            <Mouse render = { mouse => ( <Cat mouse =  {mouse} /> ) } />
            
            // ...
        );
    }
}

Context

Context provides a way to pass data through the top-down component tree without having to pass props down manually at the intermediate components.

Only context consumers inside the context provider could consume the data. Context is also top-down hierarchy.

Context makes component reuse more difficult, use it if the same data needs to be accessible by many components in the tree, and at different nesting levels.

If only to avoid passing props, component composition is a simpler alternative solution: lifting the components that use these props up to the top component that have these props, then pass them as one or more props through the intermediate components, but this method also makes the high-level components to focus on the low-level components.

In function-based components, use the React.useContext Hook to get the context value.

More details here:
https://reactjs.org/docs/context.html

Error boundaries

Error boundaries are React components that handle and recover JavaScript errors anywhere in their child component tree instead of crash the component tree.

Error boundaries apply only to errors that are thrown in:

  • constructors
  • lifecycle methods
  • during rendering

But don’t catch errors from:

  • event handlers
  • asynchronous code(such as HTTP requests, setTimeout)
  • the error boundary itself

Error handling: getDerivedStateFromError() -> componentDidCatch() -> render().

The getDerivedStateFromError method receives the error and returns a value to update state, don’t make something that have side-effects, if have to do, use the componentDidCatch method instead. The rendered content is handled using the mounting phase of the component lifecycle, so could show error messages and include the original components with the special children prop, or do something to recover.

Refs

The Refs feature provides access to the HTML elements rendered by a component after they have been added to the DOM, such as invoking the focus method on the input element. This is another way except props that parent components interact with their children.

Refs are not assigned until React invokes the render method. Ref updates happen before componentDidMount or componentDidUpdate, the two lifecycle methods can access the elements with a ref. If the component is re-created, the componentWillUnmount can be used to access the refs.

When to use

  • Use features of a specific HTML element, such as focus, selection;
  • Triggering imperative animations;
  • Integrating with third-party DOM libraries;

Use refs only as the last resort, a component that makes excessive use of refs is difficult to manage.

What it is

  • A reference to a DOM node

    export class Profile extends Component {
        constructor(props) {
            super(props);
            
            // ...
            
            this.nameRef = React.createRef();
        }
        
        focusTextInput = () => this.nameRef.current.focus();
        
        render() {
            return (
                <>
                {/* ... */}
                
                <input type="text" ref = { this.nameRef } />
                
                {/* ... */}
                </>
            );    
        }
    }

    this.nameRef.current is the <input> element.

    export function FunctionBasedProfile(props) {
        const nameRef = React.useRef(null);
        
        function click() {
            nameRef.current.focus();
        }
        
        return (
            // ...
            
            <input type="text" ref={nameRef} />
            
            // ...
        );
    }

    nameRef.current is the <input> element.

  • The mounted instance of a class-based component

    // ...
    
    constructor(props) {
        // ...
        
        this.textInput = React.createRef();
        
        // ...
    }
    
    componentDidMount () {
        // call the Profile component's focusTextInput method
        this.textInput.current.focusTextInput();
    }        
    
    // ...
    
    render() {
        return <Profile ref={this.textInput} />
    }    
    
    // ...

    this.textInput.current is an instance of the Profile component rather than a DOM node.

  • Function-based components don’t have instances, can’t use refs on them directly. Could use forwardRef (possibly in conjunction with useImperativeHandle) to take a ref.

Exposing DOM Refs to Parent Components

Callback Refs

Could pass a function to the ref attribute. The function receives a React component or an HTML element as its argument which is guaranteed to be up-to-date before componentDidMount or componentDidUpdate fires. React will call the callback function when the component mounts and unmounts. If the callback function is inline, it would be call twice during updates: one is to clear the old ref, one is to set up the new ref, could define the callback function as a class method and bind the method to the class, this shouldn’t matter in most cases.

Portals

ReactDOM.createPortal(child, container)

child is any component returned by the render method of class-based components or the function-based components;

container is a DOM element;

// class-based
class ClsPortal extends Component {
    render() {
        // ...
        
        return ReactDOM.createPortal(this.props.children, container);
    }
}

class Parent extends Component {
    // ...
    
    render() {
        return <ClsPortal> <AnyChildComponent /> </ClsPortal>
    }
}
// function-based
function FnPortal(props) {
    // ...
    
    return ReactDOM.createPortal(props.children, container);
}

function Parent(props) {
    // ...
    
    return <FnPortal> <AnyChildComponent /> </FnPortal>;
}

They render AnyChildComponent to the container DOM node instead of the Parent component.

A portal allows a component to render its content into a specific DOM node that exists outside the DOM hierarchy of the parent component.

If placing a portal in a component (Parent), the portal behaves like a normal child component, features like context, event bubbling work normally and don’t care it is a portal, the component (Parent) would catch uncaught event from the portal, not the portal attached DOM node (container).

Can’t use a portal with an element using a ref. Portals are used during the render process, Refs are assigned after the process.

Hooks

Hooks are a new addition in React 16.8, it doesn’t break anything. We could use state and other React features without writing a class.

It is hard to reuse stateful logic between components, render props and higher-order components(HOC) try to solve the problem, but the two patterns require to restructure the components, Hooks allow to reuse stateful logic without changing the component hierarchy.

Hooks are functions that let you “hook into” React state and lifecycle features from function-based components.

function Clock() {
    const [date, setDate]=useState(new Date());
    
    useEffect(() => document.title = `Current time: ${date.toDateString()}`);
    
    // ...
}    

useState and useEffect are Hooks. Use useState to get and set state. useEffect will be invoked after every render(mount phase, update phase).

useState

const [XXX, setXXX] = useState(initialXXX);
const [XXX, setXXX] = useState(() => { /* expensive computation */ });  // Lazy initialization

// Example:
const [count, setCount] = useState(0);

// XXX is a state, setXXX is the function to update it;

// setXXX syntax:
setXXX( expression );

setXXX(prevState => nextState);
  • The function returned by useState doesn’t automatically merge updated objects, it simply accepts its argument as the new state and overwrite the old one. useReducer is more suited for managing multiple sub-values state objects;
  • If the new state is computed using the previous state, pass a function instead of an expression to the function returned by useState when updating state;
  • If the initial value of a state is an expensive computation, pass a function instead of a normal expression to useState;
  • If setXXX returns a same value as the current state, React doesn’t render the children and fire the effects, but it doesn’t mean that React doesn’t render any component before making the decision. Use useMemo to optimize expensive calculations when rendering;

useEffect

useEffect(() => {
    // ...
    
    return () => {
        
        // ...
        
    }    
}, [condition1, condition2])

/*
 * The returned cleanup function and the condition array are optional;
 * The condition array is not passed as arguments to the effect function;
 */
  • Do works that have side effects during rendering, such as timer, logging. React might pause, abort, restart the render;
  • It runs both after the first render and after every update;
  • Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen, they are deferred. For effects that maintain DOM to show immediately, use useLayoutEffect instead;
  • If the effect returns a function, the function is a cleanup function that does things like componentWillUnmount(unmount phase), the function is invoked before running the next effect and during unmounting;
  • useEffect has an optional second argument, pass an array to it as a condition to determine whether to run the effect and its cleanup function. If the array is an empty array, the effect only runs once and its cleanup function is only invoked during unmounting;
  • Could call useEffect multiple times in a function-based component, and they are independent, so invoke the cleanup function before running the next effect to avoid bugs;
  • Every referenced value inside the effect function should appear in the condition array, including props and the state;
  • Should declare functions needed by the effect function inside of it, it is easy to see what values are depended on. Functions that don’t use any value from the component scope could be moved out. Also could use useCallback to wrap functions, then the wrapper appears in the condition array, the effect depend on it;

useLayoutEffect

  • The signature is identical to useEffect;
  • useLayoutEffect fires after a component is mounted and before the browser updates the screen. It is in the same phase as componentDidMount and componentDidUpdate;

useContext

const value = useContext(MyContext);
import React, {useContext} from 'react';
import './App.css';

function Text() {
    const curTheme = useContext(ThemeContext);

    return <div style={{top: 0, left: 0, bottom: 0, right: 0, position: 'fixed',
        zIndex: -1, background: curTheme.background}}/>;
}

const themes = {
    light: {
        background: "#eeeeee"
    },
    dark: {
        background: "#222222"
    }
};

const ThemeContext = React.createContext(themes.light);

function App() {
    return (
        <ThemeContext.Provider value={themes.dark}>
            <Text />
        </ThemeContext.Provider>
    );
}

export default App;

A changed context value trigger a rerender of the Text component. React.memo or shouldComponentUpdate checks props and/or state to prevent an unnecessary rerender, they can’t prevent context.

Putting a callback function into the context to let child components to call it and update the context values.

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

reducer = (state, action) => nextState;

Calling dispatch(action) will call the reducer;

initialArg is the initial state;

init(initialArg) will be used to create the initial state lazily (the state is the result of an expensive computation, and will be executed only on the initial render) or to reset the state later;

  • An alternative to useState;
  • Use it when having complex state logic that involves multiple sub-values;
  • Use it when the next state depends on the previous one;
  • Use it when components trigger deep updates. Could pass the dispatch function down as context or props. The function is stable and won’t change on re-renders, reading it doesn’t cause a re-render, unlike state;
  • If the dispatch function returns a same value as the current state, React doesn’t render the children and fire the effects, but it doesn’t mean that React doesn’t render any component before making the decision. Use useMemo to optimize expensive calculations when rendering;

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// The condition array is not passed as arguments to the function;
  • Returns a memoized value, it can be reused on the next render;
  • Use it to optimize performance when have expensive computations during rendering, the code should also work without it;
  • If no condition array is provided, a new value will be computed on every render. If you pass an empty array, the values in the list never change, so useMemo could always return the stored value;
  • Every value referenced inside the computeExpensiveValue function should appear in the condition array, like useEffect;

useCallback

const memoizedCallback = useCallback(([arguments]) => { /* ... */ }, [a, b]);

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps);

The condition array is not passed as arguments to the callback function;

The callback might have arguments;

  • Returns a memoized version of the callback if one of the conditions has changed;
  • Every value referenced inside the callback function should appear in the condition array, like useEffect;
  • It could be passed as a prop to child components, re-render the child components only when its dependencies and itself are changed. Leave the logic and data in the parent component;
function MyButton(props) {
    const [count, setCount] = useState(1);
    
    // create a new handleClick function every time when entering the MyButton function
    const handleClick = () => setCount(c => c + 1);
    
    return <>
        <p>count: {count}</p>
        <button onClick={handleClick}>Click</button>
        </>
}

// use useCallback
function MyButton(props) {
    const [count, setCount] = useState(1);
    
    // Only create the handleClick function once
    const handleClick = useCallback(() => setCount(c => c + 1), []);
    
    return <>
        <p>count: {count}</p>
        <button onClick={handleClick}>Click</button>
        </>
}

// Sometimes don't use useCallback in this way
function MyButton(props) {
    const [count2, setCount2] = useState(1);
    const [count3, setCount3] = useState(1);
    
    const handleClick = useCallback(() => 
        {setCount2(c => c + 1); setCount3(c => c + count2}
        , []);
        
    return <>
        <p>count2: {count2}</p>
        <p>count3: {count3}</p>
        <button onClick={handleClick}>Click</button>
        </>
}

/*
 * the value of count2 in the setCount3 function is always 1, 
 * useCallback returns a memoized callback. If change 
 * count2 to this style:
 * const [count2, setCount2] = useState({obj: {num: 1}}), 
 * it works, React does shallow copy for count2 every time, but
 * the {num: 1} object is always same, the callback function 
 * can read the increased value of num.
 */

The difference between useCallback and useMemo: useCallback returns a memoized function, useMemo returns a memoized value.

useRef

const refContainer = useRef(initialValue);

refContainer is mutable;

refContainer has a .current property whose value is initialized to initialValue;

refContainer will persist for the full lifetime of the component;

  • Could pass any normal value to the .current property, as well as a DOM node on the ref attribute of a component;
  • The ref object is stable and won’t change on re-renders;
  • useRef doesn’t notify when the .current property changes and doesn’t cause a re-render;

Callback Refs:

function CallbackRef() {
    const cbRef = useCallback(node => { /* ... */ }, []);
    return <h1 ref = { cbRef }/>;
}    

The callback ref will be called only when the component mounts(attach a DOM node) and unmounts(detach a DOM node).

useImperativeHandle

  • useImperativeHandle customizes the element instance that is exposed to parent components, such as replacing the native focus function with your own, changing the returned ref of a table to a table cell;
  • useImperativeHandle should be used with forwardRef. forwardRef lets a component pass the received ref further down to a child;

useDebugValue

useDebugValue(value);

useDebugValue(value, value => { /* ... */ });
  • useDebugValue displays a label for custom hooks in React DevTools;

Hook rules

  • Only call Hooks at the top level of function-based components, not inside loops, conditions, or nested functions. React relies on the order of Hooks to restore the state during re-render;
  • Only call Hooks from function-based components or custom Hooks;

Custom Hooks

  • The Hook name should always start with use;

There are no Hook equivalents to getSnapshotBeforeUpdate, getDerivedStateFromError and componentDidCatch lifecycles yet.

Strict mode

Strict mode checks potential problems. It doesn’t render any visible UI and runs in development mode only.

An important functionality is to detect unexpected side effects. The frequently used methods, constructor, render and setState, might be called more than once. These methods should not contain side-effects, it means giving same arguments to these functions should get the same results.

For finding side-effects early, React invokes constructor, render, setState, functions passed to useState, useMemo and useReducer, function-based component body and other methods or functions(here) twice.

create-react-app created projects have the following code in index.js:

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);

It opens the strict mode by default.

function useCount(props) {
    const [count, setCount] = useState(0);

    console.log(`call useCount: ${count}`);

    useEffect(() => {
        console.log(`set count from ${count} to ${count + 1}`);
    }, [props]);

    return count;
}

function MySpan() {
    const val = useCount('nothing');

    console.log(`call MySpan: ${val}`);

    return <span>{val}</span>
}

function App() {
    return <MySpan/>;
}

The code in App.js will print the following log:

call useCount: 0
call MySpan: 0
call useCount: 0
call MySpan: 0
set count from 0 to 1
call useCount: 1
call MySpan: 1
call useCount: 1
call MySpan: 1