The introduction

React has been transitioning from Class to function components since 2019. Although functional components are very convenient, there are still some confusion when using them, which can sometimes lead to some strange problems. Here, I’d like to compile some best practices from the official website and blog posts, as well as my own accumulation, in case of need.

Hook doesn’t affect your understanding of React concepts. Instead, hooks provide a more straightforward API for known React concepts: props, state, Context, refs, and lifecycle. As we’ll see later, hooks also provide a more powerful way to combine them.

Here are some of the official motivations for which hooks were proposed and used.

Reusing state logic between components is difficult

You can use hooks to extract state logic from components so that it can be individually tested and reused. Hooks allow you to reuse state logic without modifying the component structure. This makes it easier to share hooks between components or within a community.

Complex components become difficult to understand

We often maintain components that start out simple, but gradually become overwhelmed with state logic and side effects. Each life cycle often contains some unrelated logic. For example, components often get data in componentDidMount and componentDidUpdate. However, the same componentDidMount may also contain a lot of other logic, such as setting up event listeners that need to be cleared later in componentWillUnmount. Code that is related and needs to be modified against each other is split, while completely unrelated code is grouped together in the same method. This is very buggy and leads to logical inconsistencies.

In most cases, it is not possible to break components down into smaller granularity because state logic is ubiquitous. This makes testing a bit of a challenge. This is also one of the reasons many people use React in conjunction with the state management library. However, this often introduces a lot of abstraction and requires you to switch back and forth between different files, making reuse more difficult.

To solve this problem, hooks break the interrelated parts of a component into smaller functions (such as setting up subscriptions or requesting data) rather than enforcing a lifecycle partition. You can also use Reducer to manage the internal state of components and make them more predictable.

Hard to understand class

In addition to the difficulties of code reuse and code management, we found class to be a big barrier to learning React. You have to understand how this works in JavaScript, which is vastly different from other languages. And don’t forget to bind event handlers. Without a stable syntax proposal, the code is very redundant. You can understand props, state, and the top-down data flow pretty well, but you can’t do anything about class. Even experienced React developers disagree about the difference between a function component and a class component, and even how the two components are used. To address these issues, hooks allow you to use more React features in non-class situations. Conceptually, the React component has always been more like a function. Hook embraced functions without sacrificing the spirit of React. Hooks provide solutions to problems without learning complex functional or reactive programming techniques.

The official Hooks

useState

UseState is the first Hook we’ll look at, and its purpose is to manage state.

const [state, setState] = useState(initialState);Copy the code

Returns a state and a function to update the state.

During initial rendering, the state returned is the same as the value of the first parameter passed in.

The setState function is used to update state. It receives a new state value and queues a re-rendering of the component.

setState(newState);Copy the code

In subsequent rerenders, the first value returned by useState will always be the latest state after the update.

Pay attention to

React ensures that the setState function identifier is stable and does not change when the component is re-rendered. This is why it is safe to omit setState from useEffect or useCallback’s dependency list.

Update objects are not merged automatically

Unlike the setState method in the Class component, useState does not automatically merge update objects. You can merge updated objects using a functional setState in conjunction with the expansion operator.

setState(prevState => {// You can also use object.assignreturn{... prevState, ... updatedValues}; });Copy the code

UseReducer is another option that is better suited for managing state objects with multiple child values.

Lazy initial state

The initialState parameter is only used in the initial rendering of the component and will be ignored in subsequent renderings. If the initial state needs to be computed through complex calculations, we can pass in a function that computes and returns the initial state, which is only called during the initial render:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});Copy the code

useEffect

UseEffect is an Effect Hook that gives function components the ability to manipulate side effects. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in the Class component, but has been consolidated into an API.

When you call useEffect, you’re telling React to run your “side effect” function after making changes to the DOM. Because side functions are declared inside the component, they can access the props and state of the component. By default, React calls side effects after every render — including the first one. Side effects functions can also specify how to “clear” side effects by returning a function. However, this cleanup is triggered only when the component is killed.

Effect that does not need to be cleaned

Sometimes, we just want toRun some extra code after React updates the DOM.Sending network requests, manually changing the DOM, and logging are all common operations that don’t need to be cleaned up. Because we can ignore them after we’ve done these operations.



What does the useEffect do? By using this Hook, you can tell the React component that it needs to do something after rendering. React saves the function you passed (we’ll call it “effect”) and calls it after a DOM update.

Why is useEffect called inside a component? Putting useEffect inside the component allows us to access the count State variable (or other props) directly in effect. We don’t need a special API to read it — it’s already saved in function scope. Hook uses JavaScript’s closure mechanism rather than introducing a specific React API when JavaScript already provides a solution.

Will useEffect be executed after every render? Yes, by default, it is executed after the first render and after every update. (We’ll talk about how to control it later.) You might be more comfortable with the idea that an effect happens “after rendering,” without having to worry about “mount” or “update.” React ensures that the DOM is updated every time an Effect is run.

Tip: Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser update screen, making your application seem more responsive. In most cases, effects do not need to be executed synchronously. In individual cases (such as measuring layout), there is a separate useLayoutEffect Hook for you to use, with the same API as useEffect.

Effects that need to be cleared

Earlier, we looked at how to use side effects that don’t need to be removed, and some side effects that do. Such as subscribing to external data sources. In this case, cleaning is very important to prevent memory leaks!

Why return a function in effect? This is the optional clearing mechanism for Effect. Each effect can return a cleanup function. This puts the logic for adding and removing subscriptions together. They are all part of Effect.

React when to remove an effect? React performs cleanup operations when components are uninstalled. As you learned earlier, an effect is executed every time it is rendered. This is why React cleans up the previous effect before executing the current effect. We’ll talk later about why this helps avoid bugs and how to skip this behavior when you encounter performance problems.

Run Effect every time you update it

In this Effect, we unsubscribe the function and subscribe to the function with the new status props. Friend. id every time the props.

No specific code is required to handle the update logic, because useEffect handles it by default. It cleans up the previous effect before calling a new one. To illustrate this, here is a chronological sequence of possible subscribe and unsubscribe operation calls:



Optimize performance by skipping Effect

In some cases, performing cleanup or effect after every render can cause performance problems. In the Class component, we can resolve this by adding the prevProps or prevState comparison logic to componentDidUpdate: prevProps

This is a common requirement, so it’s built inuseEffectHook API. You can notify React if certain values don’t change between rerendersskipA call to effect simply passes an array asuseEffectThe second optional argument to:







Note:

If you use this optimization, make sure that the array contains all variables in the external scope that will change over time and be used in effect, otherwise your code will reference the old variables in the previous rendering. See the documentation for more information on how to handle functions and what to do when arrays change frequently.

If you want to execute effect once (only when the component is mounted and unmounted), you can pass an empty array ([]) as the second argument. This tells React that your effect doesn’t depend on any value in props or state, so it never needs to be repeated. This is not a special case — it still follows the way dependent arrays work.

If you pass an empty array ([]), the props and state inside effect will always have their initial values. Although passing [] as the second argument is closer to the more familiar componentDidMount and componentWillUnmount mind-set, there are better ways to avoid repeating calls to Effect too often. In addition, remember that React waits for the browser to finish rendering the screen before delaying the useEffect call, thus making extra operations convenient.

We recommend enabling the Strict-deps rule in eslint-plugin-react-hooks. This rule warns when you add false dependencies and suggests how to fix them.

useContext

const value = useContext(MyContext);Copy the code

Receives a context object (the return value of React. CreateContext) and returns the current value of the context. The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component.

When the most recent

update is made to the component’s upper layer, the Hook triggers a rerender and uses the latest context value passed to MyContext Provider. Even if the ancestor uses React. Memo or shouldComponentUpdate, it will be rerendered when the component itself uses useContext.

Remember that the argument to useContext must be

Context object itself
:

  • Correct: useContext(MyContext)
  • Error: useContext(MyContext.Consumer)
  • Error: useContext(MyContext.Provider)

Components that call useContext are always rerendered when the context value changes. If rerendering components is expensive, you can optimize them by using Memoization.

prompt

If you’re already familiar with the Context API before you hit the Hook, it should make sense, UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component.

UseContext (MyContext) just allows you to

read

The value of the context and the changes to the subscribed context. You still need to use it in the upper component tree
<MyContext.Provider>For the underlying component

provide

The context.

Put the following code with context.provider

const themes = {
  light: {
    foreground: "# 000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "# 222222"}}; const ThemeContext = React.createContext(themes.light);function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
function ThemedButton() {
  const theme = useContext(ThemeContext);  return (    <button style={{ background: theme.background, color: theme.foreground }}>      I am styled by theme context!    </button>  );
}Copy the code

The previous example in the Context Advanced guide has been modified using hooks. You can find more information about how to Context in the link.

useReducer

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

An alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method. (If you’re familiar with Redux, you already know how it works.)

UseReducer can be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state. Also, using useReducer can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks.

Here is an example of a counter that overwrites the useState section with reducer:

const initialState = {count: 0};
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return{count: state.count - 1}; default: throw new Error(); }}function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}Copy the code

Pay attention to

React ensures that the identity of the Dispatch function is stable and does not change when the component is re-rendered. This is why it is safe to omit Dispatch from useEffect or useCallback’s dependency list.

Specify initial state

There are two different ways to initialize useReducer State, and you can choose one of them depending on your usage scenario. It is easiest to pass the initial state as the second argument to the useReducer:

const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}  );Copy the code

Pay attention to

React does not use the state = initialState convention popularized by Redux. Sometimes the initial value depends on props and therefore needs to be specified when the Hook is called. If you particularly like the above conventions, you can mimic the behavior of Redux by calling useReducer(Reducer, undefined, reducer), but this is discouraged.

Lazy initialization

You can choose to lazily create the initial state. To do this, you need to pass in the init function as the third argument to the useReducer, so that the initial state will be set to init(initialArg).

Doing so extracts the logic used to calculate state outside the Reducer, which also facilitates future actions to reset state:

function init(initialCount) {  return{count: initialCount}; }function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':      returninit(action.payload); default: throw new Error(); }}function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}Copy the code

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);Copy the code

Returns a Memoized callback function.

Passing the inline callback function and the array of dependencies as arguments to useCallback returns the Memoized version of the callback function, which is updated only when a dependency changes. This is useful when you pass callback data to child components that are optimized and use reference equality to avoid unnecessary rendering (such as shouldComponentUpdate).

UseCallback (fn, deps) is equivalent to useMemo(() => FN, deps).

Pay attention to

The dependency array is not passed as an argument to the callback function. Conceptually, though, it looks like this: all values referenced in callback functions should appear in dependency arrays. In the future, compilers will get smarter and it will be possible to create arrays automatically.

useMemo

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

Returns an memoized value.

You pass in the create function and the dependency array as arguments to useMemo, which recalculates memoized values only when a dependency changes. This optimization helps avoid costly calculations every time you render.

Remember that functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function. Operations such as side effects are used by useEffect, not useMemo.

If the dependency array is not provided, useMemo evaluates the new value each time it renders.

You can use useMemo as a means of performance optimization, but don’t use it as a semantic guarantee. In the future, React might choose to “forget” some of the previous Memoized values and recalculate them at the next rendering, such as freeing memory for off-screen components. Write code that can run without useMemo first — then add useMemo to your code to optimize performance.

Pay attention to

Dependency arrays are not passed as arguments to the Create function. Conceptually, though, it looks like this: all values referenced in the create function should appear in the dependency array. In the future, compilers will get smarter and it will be possible to create arrays automatically.

useRef

const refContainer = useRef(initialValue);Copy the code

UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component.

A common use case is to mandate access to child components:

function TextInputWithFocusButton() { const inputEl = useRef(null); Const onButtonClick = () => {// 'current' points to the text input element inputel.current.focus () mounted to the DOM; };return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}Copy the code

In essence, useRef is like a “box” that can hold a mutable value in its.current property.

You should be familiar with ref, the main way to access the DOM. If you pass the ref object to the component as

, React sets the.current property of the ref object to the corresponding DOM node, regardless of how the node changes.

However, useRef() is more useful than the ref attribute. It makes it easy to store any mutable value, similar to the way instance fields are used in class.

This is because it creates a normal Javascript object. UseRef () and create a {current:… The only difference with the} object is that useRef returns the same ref object every time it renders.

Remember that when the ref object contents change, useRef does not

Don’t

Let you know. change
.currentProperty does not cause the component to rerender. This is required if you want to run some code when React binds or unbinds a DOM node’s REF
The callback refTo implement.


Callback Ref (measure DOM)

The basic way to get the position or size of a DOM node is to use a callback ref. React calls the callback whenever a ref is attached to another node. Here’s a little demo:

function MeasureExample() {
  const [height, setHeight] = useState(0);
  const measuredRef = useCallback(node => {    
        if(node ! == null) {setHeight(node.getBoundingClientRect().height); }} []);return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}Copy the code

In this case, we chose not to use useRef because ref does not take the value of the current ref when it is an object

change

Let us know. Using callback ref ensures that
Even if the child component delays the display of the measured node(for example, in response to a click), we can still receive relevant information in the parent component to update the measurement results.

Notice that we passed [] as the dependency list for useCallback. This ensures that the Ref callback will not change when rendering again, so React will not call it unnecessarily.

In this case, the callback ref is called only when the component is mounted and unmounted, and because the

component does not change height each time it is re-rendered, the callback REF does not need to be triggered. If you want to be notified when a component resizes, use ResizeObserver or use Hooks from another third party. If you like, you can extract this logic as a reusable Hook:

function MeasureExample() {
  const [rect, ref] = useClientRect();  return( <> <h1 ref={ref}>Hello, world</h1> {rect ! == null && <h2>The above header is {Math.round(rect.height)}px tall</h2> } </> ); }function useClientRect() {
  const [rect, setRect] = useState(null);
  const ref = useCallback(node => {
    if(node ! == null) {setRect(node.getBoundingClientRect()); }} []);return [rect, ref];
}Copy the code

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])Copy the code

UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref. In most cases, imperative code like ref should be avoided. UseImperativeHandle should be used with the forwardRef:

functionFancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }}));return<input ref={inputRef} ... / >; } FancyInput = forwardRef(FancyInput);Copy the code


In this case, the parent component rendering
can call inputref.current.focus ().

useLayoutEffect

Its function signature is the same as useEffect, but it calls Effect synchronously after all DOM changes. You can use it to read DOM layouts and trigger rerenders synchronously. The update schedule inside useLayoutEffect is refreshed synchronously before the browser performs the drawing.

Use standard Useeffects whenever possible to avoid blocking visual updates.


prompt

If you are migrating code from a class component to a function component that uses hooks, note that useLayoutEffect is the same as the calling phase for componentDidMount and componentDidUpdate. However, we recommend you start with useEffect and only try use elayouteffect if it goes wrong.

If you’re using server-side rendering, remember,

No matter

useLayoutEffect

or

useEffectCannot be executed before the Javascript code is loaded. Is that why the server side rendering component was introduced
useLayoutEffectCode generates a React alarm. To solve this problem, move the code logic to
useEffect(if this logic is not required for the first rendering), or delay displaying the component until the client finishes rendering (if until
useLayoutEffectExecute before HTML is displayed in case of disorder).

To exclude components that depend on layout effect from HTML rendered on the server, conditional rendering can be done using showChild &&
and useEffect(() => {setShowChild(true); }, []) delay the presentation of components. This way, the UI will not appear as cluttered as before until the client rendering is complete.

useDebugValue

useDebugValue(value)Copy the code

UseDebugValue can be used to display tags for custom hooks in the React developer tool.

For example, a custom Hook named useFriendStatus described in the “Custom Hook” section:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null); / /... // Display the tag // LLDB next to the Hook in developer tools."FriendStatus: Online"  
  useDebugValue(isOnline ? 'Online' : 'Offline');
  return isOnline;
}Copy the code

Best practices

In fact, not only objects, functions are also independent each time they are rendered. This is the Capture Value feature.

Is it safe to omit functions from the dependency list?

Generally speaking, it’s not safe.

function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);  }
  useEffect(() => {
    doSomething(); } []); // 🔴 this is not safe (it calls'doSomething 'uses' someProp')}Copy the code

It is difficult to remember which props and states are used by functions outside effect. That’s why you usually want to have an effect, right

internal

To declare the function that it needs.This makes it easy to see which values in the component scope the effect depends on:

function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);    }
    doSomething(); }, [someProp]); // ✅ security (our effect only uses' someProp ')}Copy the code

If we still don’t have any value in the component scope after this, we can safely specify it as [] :

useEffect(() => {
  function doSomething() {
    console.log('hello');
  }
  doSomething(); } []); // ✅ is safe in this example because we are not using any * values in the component scopeCopy the code

Depending on your use case, here are some other options.

Pay attention to

We provide an Exhaustive -deps ESLint rule as part of the eslint-plugin-react-hooks package. It will help you identify components that are not handling updates consistently.

Let’s see how that matters.

If you specify a dependency list as the last parameter to useEffect, useMemo, useCallback, or useImperativeHandle, it must contain all the values in the callback and participate in the React data stream. This includes props, state, and anything derived from them.

Only if the function (and the function it calls) doesn’t reference props, state, and the values derived from them can you safely omit them from the dependency list. The following example has a Bug:

function ProductPage({ productId }) {
  const [product, setProduct] = useState(null);
  async function fetchProduct() {
    const response = await fetch('http://myapi/product/'+ productId); // productId prop const json = await response.json();setProduct(json); } useEffect(() => { fetchProduct(); } []); // 🔴 is invalid because 'fetchProduct' uses' productId '//... }Copy the code

The recommended fix is to move that function to your effect

internal

. This makes it easy to see what props and state your effect is using and make sure they are declared:

function ProductPage({ productId }) {
  const [product, setProduct] = useState(null); UseEffect (() => {// After moving this function inside effect, we can clearly see the value it uses. asyncfunction fetchProduct() {      const response = await fetch('http://myapi/product/' + productId);      const json = await response.json();      setProduct(json); } fetchProduct(); }, [productId]); // ✅ is valid because our effect is only productId //... }Copy the code

This also allows you to handle out-of-order responses with local variables inside effect:

useEffect(() => {
    let ignore = false;    async function fetchProduct() {
      const response = await fetch('http://myapi/product/' + productId);
      const json = await response.json();
      if(! ignore)setProduct(json);    }
    fetchProduct();
    return () => { ignore = true };  }, [productId]);Copy the code

Let’s move this function inside effect so it doesn’t have to appear in its dependency list.

prompt

Check out this small demo and this article to learn more about how to Hook data retrieval.

If for some reason you

Can’t

There are other ways to move a function inside an effect:

  • You can try to move that function out of your component. That way, the function definitely doesn’t depend on any props or state, and it doesn’t have to be in the dependency list.
  • If the method you are calling is a pure calculation and can be called at render time, you can call it outside effect instead and have effect depend on its return value.
  • If you have to, you canAdd the function to the dependent but of effect
    Wrap up its definition

    useCallbackThe hooks. This ensures that it does not change with rendering unless

    Its own

    Has changed:

functionConst fetchProduct = useCallback(() => {//... Does something with productId ... }, [productId]); // ✅ all dependencies of useCallback are specifiedreturn <ProductDetails fetchProduct={fetchProduct} />;
}
functionProductDetails({ fetchProduct }) { useEffect(() => { fetchProduct(); }, [fetchProduct]); // ✅ useEffect all dependencies are specified //... }Copy the code

Note that in the above case, we need the function to appear in the dependency list. This ensures that changes to productId Prop for ProductPage will automatically trigger refetching of ProductDetails.

How do I read a frequently changing value from useCallback?

Pay attention to

We recommend passing dispatches down in context rather than using separate callbacks in props. The following approach is only mentioned here for documentation integrity and as a way out.

Also note that this pattern may cause problems in parallel mode. We plan to offer a more reasonable alternative in the future, but the safest solution right now is to always let the callback fail if the value it depends on changes.

In some rare cases, you might need to remember a callback using useCallback, but because the internal function must be recreated frequently, it doesn’t work very well. If the function you want to remember is an event handler and is not used during rendering, you can use ref as an instance variable and manually store the last submitted value in it:

function Form() {
  const [text, updateText] = useState(' '); const textRef = useRef(); useEffect(() => { textRef.current = text; // write it to ref}); const handleSubmit = useCallback(() => { const currentText = textRef.current; // Read it from ref alert(currentText); }, [textRef]); // Do not recreate handleSubmit like [text]return (
    <>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}Copy the code

This is a cumbersome pattern, but it means that you can use this way to optimize if you need to. You’ll feel better if you extract it as a custom Hook:

function Form() {
  const [text, updateText] = useState(' '); // Remember if 'text' changes: const handleSubmit = useEventCallback(() => {alert(text); }, [text]);return (
    <>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}
function useEventCallback(fn, dependencies) {  const ref = useRef(() => {
    throw new Error('Cannot call an event handler while rendering.');
  });
  useEffect(() => {
    ref.current = fn;
  }, [fn, ...dependencies]);
  return useCallback(() => {
    const fn = ref.current;
    return fn();
  }, [ref]);
}Copy the code

In any case, we do not recommend using this pattern, but show it here for the integrity of the documentation. Instead, we prefer to avoid passing callbacks further down.

Bypass the Capture Value

Using useRef

You can bypass Capture Value features with useRef. Ref can be thought of as a unique reference in all Render processes, so all assignments or values to ref get only one final state, without isolation between Render processes.

function Example() {
  const [count, setCount] = useState(0);
  const latestCount = useRef(count);
  useEffect(() => {
    // Set the mutable latest value
    latestCount.current = count;
    setTimeout(() => {
      // Read the mutable latest value
      console.log(`You clicked ${latestCount.current} times`);
    }, 3000);
  });
  // ...
}Copy the code

Alternatively, ref is Mutable, and state is Immutable.

UseState callback mode

The example above uses count, but this code is awkward because you rely on external variables in an Effect that you only want to execute once.

To be honest, one has to find a way not to rely on external variables:

useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  return() => clearInterval(id); } []);Copy the code

SetCount also has a function callback mode where you don’t need to care what the current value is, just modify the “old value”. This way the code always runs in the first Render, but always has access to the latest state.

Use the useReducer function

Decouple updates from actions:

const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
  const id = setInterval(() => {
    dispatch({ type: "tick" }); // Instead of setCount(c => c + step);
  }, 1000);
  return () => clearInterval(id);
}, [dispatch]);Copy the code

This is a local “Redux”, since the update becomes dispatch({type: “tick”}), no variables need to be relied on in the action of calling the update, no matter how many variables need to be relied on during the update. The specific update operations can be written in the Reducer function. The online Demo.

Dan also compares useReducer to the goldfinger mode of Hooks because it sufficiently circumvents the Diff mechanism,

But it does take care of the pain points!

Use useCallback to wrap the function

function Parent() {
  const [query, setQuery] = useState("react"); // ✅ Molded identity Until query Changes Const fetchData = useCallback(() => {const url ="https://hn.algolia.com/api/v1/search?query=" + query;
    // ... Fetch data and return it ...
  }, [query]); // ✅ Callback deps are OK
  return <Child fetchData={fetchData} />;
}
function Child({ fetchData }) {
  let [data, setData] = useState(null);
  useEffect(() => {
    fetchData().then(setData); }, [fetchData]); // ✅ deps are OK //... }Copy the code

Because functions also have the Capture Value feature, useCallback wrapped functions can be used as normal variables to rely on useEffect. All useCallback does is return a new function reference when its dependency changes, trigger the useEffect dependency change, and activate its re-execution.


Custom Hooks

Custom hooks are a convention that naturally follows Hook design, not a Feature of React.

Must custom hooks start with “use”? It has to be. This agreement is very important. Otherwise, React will not automatically check if your hooks violate Hook rules, since it is impossible to determine whether a function contains calls to its internal hooks.

Will using the same Hook in two components share state? Don’t. Custom hooks are a form of reuse

State logic

So every time a custom Hook is used, all states and side effects are completely isolated.

How do custom hooks get independent state? Every time

call

Hook, which gets independent state. Because we called it directly
useFriendStatusFrom the React perspective, our component just calls
useState
useEffect. As we did in the
Previous sectionIn the
To know theAgain, we can call multiple times within a component
useState
useEffectThey are completely independent.

Pass information between multiple hooks

Since hooks are functions themselves, we can pass information between them.

We’ll use another component in the chat program to illustrate this point. This is a chat message receiver selector that displays whether the currently selected friend is online:


const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross'},];function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);  const isRecipientOnline = useFriendStatus(recipientID);
  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}Copy the code

We saved the currently selected friend IDS in the recipientID status variable, and updated the state when the user selected other friends from

Since useState provided us with the latest value of the recipientID status variable, we could pass it as a parameter to our custom useFriendStatus Hook:

  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);Copy the code

So that we can know

The currently selected

Are your friends online? When we choose different friends and update
recipientIDState variable,
useFriendStatusHook will unsubscribe the previously selected friend and subscribe to the status of the newly selected friend.

useLegacyState

If you missed automatic merge, you can write a custom useLegacyState Hook to merge updates to object state. However, we recommend splitting state into multiple state variables, each containing different values that change at the same time.

Gets props or state of the previous round?

This can be done manually via ref:

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);  return <h1>Now: {count}, before: {prevCount}</h1>;
}
function usePrevious(value) {  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}Copy the code

Notice how this applies to props, state, or any other computed value.

function Counter() {
  const [count, setCount] = useState(0);
  const calculation = count + 100;
  const prevCalculation = usePrevious(calculation);  // ...Copy the code

Given that this is a relatively common usage scenario, it is likely that React will ship with a usePrevious Hook in the future.

See Derived State recommendation schema.


Refer to the article

  • The official documentation
  • Close reading complete guide to useEffect
  • Umi Hooks