admin管理员组

文章数量:1023571

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

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 ponents:

  • Inner takes a prop and has state. This is the ponent 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 ponent. A live version of this code can be found on codesandbox. How can I migrate it to a functional ponent:

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 ponent 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 ponents 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 ponents : This question predates stateful functional ponents. The accepted answer explains how functional ponents don't need shouldComponentUpdate since they are stateless.

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

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 ponents:

  • Inner takes a prop and has state. This is the ponent 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 ponent. A live version of this code can be found on codesandbox. How can I migrate it to a functional ponent:

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 ponent 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 ponents 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 ponents : This question predates stateful functional ponents. The accepted answer explains how functional ponents don't need shouldComponentUpdate since they are stateless.

Share Improve this question edited Apr 1, 2020 at 8:00 lhk asked Mar 31, 2020 at 16:47 lhklhk 30.4k36 gold badges137 silver badges220 bronze badges 1
  • React.memo: "This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs." - Could you present us the case where you might need it? Maybe we can suggest other solutions such as introducing the key property. But its hard to reason about the problem without the case itself. – Caramiriel Commented Mar 31, 2020 at 17:00
Add a ment  | 

4 Answers 4

Reset to default 1

React memo do not stop state changes

React.memo only checks for prop changes. If your function ponent 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/docs/react-api.html#reactmemo

React is by design driven by setState -> re-render loop. Props change is in fact a setState somewhere in parent ponents. 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.

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

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

By memoizing the Inner ponent 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 />
    </>
  );
}


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

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

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();
}

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

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 ponents:

  • Inner takes a prop and has state. This is the ponent 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 ponent. A live version of this code can be found on codesandbox. How can I migrate it to a functional ponent:

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 ponent 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 ponents 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 ponents : This question predates stateful functional ponents. The accepted answer explains how functional ponents don't need shouldComponentUpdate since they are stateless.

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

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 ponents:

  • Inner takes a prop and has state. This is the ponent 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 ponent. A live version of this code can be found on codesandbox. How can I migrate it to a functional ponent:

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 ponent 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 ponents 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 ponents : This question predates stateful functional ponents. The accepted answer explains how functional ponents don't need shouldComponentUpdate since they are stateless.

Share Improve this question edited Apr 1, 2020 at 8:00 lhk asked Mar 31, 2020 at 16:47 lhklhk 30.4k36 gold badges137 silver badges220 bronze badges 1
  • React.memo: "This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs." - Could you present us the case where you might need it? Maybe we can suggest other solutions such as introducing the key property. But its hard to reason about the problem without the case itself. – Caramiriel Commented Mar 31, 2020 at 17:00
Add a ment  | 

4 Answers 4

Reset to default 1

React memo do not stop state changes

React.memo only checks for prop changes. If your function ponent 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/docs/react-api.html#reactmemo

React is by design driven by setState -> re-render loop. Props change is in fact a setState somewhere in parent ponents. 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.

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

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

By memoizing the Inner ponent 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 />
    </>
  );
}


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

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

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();
}

本文标签: