简体   繁体   中英

shouldComponentUpdate equivalent for functional component, to ignore state changes

My code has a component that takes both props and has its own internal state.
The component should rerender ONLY when its props change. State changes should NOT trigger a rerender.
This behaviour can be implemented with a class based component and a custom shouldComponentUpdate function.
However, this would be the first class based component in the codebase. Everything is done with functional components and hooks. Therefore I would like to know whether it is possible to code the desired functionality with functional components.

After a few answers that didn't approach the real problem, I think I have to reformulate my question. Here is a minimal example with two components:

  • Inner takes a prop and has state. This is the component in question. It must not rerender after state changes. Prop changes should trigger a rerender.
  • Outer is a wrapper around inner. It has no meaning in the scope of this question and is only there to give props to Inner and to simulate prop changes.

To demonstrate the desired functionality I have implemented Inner with a class based component. A live version of this code can be found on codesandbox . How can I migrate it to a functional component:

Inner.tsx :

import React, { Component } from 'react'

interface InnerProps{outerNum:number}
interface InnerState{innerNum:number}

export default class Inner extends Component<InnerProps, InnerState> {
    state = {innerNum:0};

    shouldComponentUpdate(nextProps:InnerProps, nextState:InnerState){
        return this.props != nextProps;
    }
    render() {
        return (
            <button onClick={()=>{
                this.setState({innerNum: Math.floor(Math.random()*10)})
            }}>
                {`${this.props.outerNum}, ${this.state.innerNum}`}
            </button>
        )
    }
}

Outer.tsx:

import React, { useState } from "react";
import Inner from "./Inner";

export default function Outer() {
  const [outerState, setOuterState] = useState(1);

  return (
    <>
      <button
        onClick={() => {
          setOuterState(Math.floor(Math.random() * 10));
        }}
      >
        change outer state
      </button>
      <Inner outerNum={outerState}></Inner>
    </>
  );
}

The official docs say to wrap the component in React.memo . But this doesn't seem to work for preventing rerenders on state change. It only applies to prop changes.

I have tried to make React.memo work. You can see a version of the code with both Outer and Inner being functional components here .

Related questions:

How to use shouldComponentUpdate with React Hooks? : This question only deals with prop changes. The accepted answer advises to use React.memo

shouldComponentUpdate in function components : This question predates stateful functional components. The accepted answer explains how functional components don't need shouldComponentUpdate since they are stateless.

React memo do not stop state changes

React.memo only checks for prop changes. If your function component wrapped in React.memo has a useState or useContext Hook in its implementation, it will still rerender when state or context change.

Ref:- https://reactjs.org/docs/react-api.html#reactmemo

Your Inner component depends on the property num of the Outer component, you can't prevent it from rendering on property change as React.memo makes properties comparison:

// The default behaviour is shallow comparison between previous and current render properties.
const areEqual = (a, b) => a.num === b.num;
export default React.memo(Inner, areEqual);

By memoizing the Inner component and removing the num dependency, it won't render on Outer rendering, see sandbox attached.

export default function Outer() {
  const [outerState, setOuterState] = useState(1);

  return (
    <>
      ...
    // v Inner is memoized and won't render on `outerState` change.
      <Inner />
    </>
  );
}

编辑 smart-silence-6xigt


If you want to implement shouldComponentUpdate with hooks you can try:

const [currState] = useState();
// shouldUpdateState your's custom function to compare and decide if update state needed
setState(prevState => {
  if(shouldUpdateState(prevState,currState)) {
    return currState;
  }
  return prevState;
});

React is by design driven by setState -> re-render loop. Props change is in fact a setState somewhere in parent components. If you don't want the setState to trigger a re-render, then why in the first place use it?

You can pull in a const state = useRef({}).current to store your internal state instead.

function InnerFunc(props) {
  const state = useRef({ innerNum: 0 }).current;
  return (
    <button
      onClick={() => {
        state.innerNum = Math.floor(Math.random() * 10);
      }}
    >
      {`${props.outerNum}, ${state.innerNum}`}
    </button>
  );
}

That said, it's still a valid question to ask: "how to implement shouldComponentUpdate in a react hook fashion?" Here's the solution:

function shouldComponentUpdate(elements, predicate, deps) {
  const store = useRef({ deps: [], elements }).current
  const shouldUpdate = predicate(store.deps)
  if (shouldUpdate) {
    store.elements = elements
  }
  store.deps = deps
  return store.elements
}

// Usage:

function InnerFunc(props) {
  const [state, setState] = useState({ innerNum: 0 })
  const elements = (
    <button
      onClick={() => {
        setState({ innerNum: Math.floor(Math.random() * 10) });
      }}
    >
      {`${props.outerNum}, ${state.innerNum}`}
    </button>
  );

  return shouldComponentUpdate(elements, (prevDeps) => {
    return prevDeps[0] !== props
  }, [props, state])
}

Noted that it's impossible to prevent a re-render cycle when setState is called, the above hook merely makes sure the re-rendered result stays the same as prev rendered result.

you should use the event that provide the browser and capture in the function before setState, like this

function setState = (e) =>{ //the e is the event that give you the browser
//changing the state
e.preventDefault();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM