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 ifshowHeader
is true.But
<div> {props.messages.length && <MessageList messages={props.messages/>} </div>
JSX still renders the
MessageList
component if the length is0
. Need to use the conditionprops.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
orbutton.module.scss
, only import it intobutton.js
, the rules only apply to the components withinbutton.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
(renamedbutton.css
tobutton.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 oncesetState
is completed and the component is re-rendered. Recommend usingcomponentDidUpdate()
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 reuseSyntheticEvent
objects and reset all the properties tonull
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 byaddEventListener()
withuseCapture
option. In React, use theonClickCapture
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- Capture phase: The event is being propagated through the target’s ancestor objects, This process starts with the
Component lifecycle
Class-based components:
- Mount phase:
constructor()
->getDerivedStateFromProps()
->render()
->componentDidMount()
- Update phase:
getDerivedStateFromProps()
->shouldComponentUpdate()
->render()
->getSnapshotBeforeUpdate()
->componentDidUpdate()
- Unmount phase:
componentWillUnmount()
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
propfunction FancyBorder(props) { return ( // ... { props.children } // ... ); }
function WelcomeDialog() { return ( <FancyBorder> {/* ... */} </FancyBorder> ); }
The
WelcomeDialog
component reuses theFancyBorder
component. Theprops.children
will be replaced by the elements included byFancyBorder
in theWelcomeDialog
component.There are
map
,forEach
,toArray
, etc. methods inReact.Children
, could use them to get advanced knowledge about these children, withReact.cloneElement
to modify them, such as adding new props.Pass components as props
function App() { return ( <SplitPane left = { <Contacts /> } right = { <Chat /> } /> ); }
Contacts
andChat
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 genericDialog
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 theProfile
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 withuseImperativeHandle
) to take a ref.
Exposing DOM Refs to Parent Components
Receive the ref through the
ref
attribute, like the above two examples, can’t work with function-based components if noforwardRef
;Receive the ref as a normal prop whose name isn’t
ref
from parent components, then pass the ref to children;Use
React.forwardRef
;More details here:
https://reactjs.org/docs/refs-and-the-dom.html
https://reactjs.org/docs/hooks-reference.html#useimperativehandle
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. UseuseMemo
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
orcomponentDidUpdate
, effects scheduled withuseEffect
don’t block the browser from updating the screen, they are deferred. For effects that maintain DOM to show immediately, useuseLayoutEffect
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 ascomponentDidMount
andcomponentDidUpdate
;
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. UseuseMemo
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, likeuseEffect
;
useCallback
const memoizedCallback = useCallback(([arguments]) => { /* ... */ }, [a, b]);
useCallback(fn, deps)
is equivalent touseMemo(() => 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 toinitialValue
;
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 theref
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 withforwardRef
.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