2

As the title says, how can I reproduce useMemo or useCallback with React class component? I am doing some heavy calculation and I don't want the component to recalculate the value every time it's rendered again. Is the only solution to convert my class component to a function component?

Roy Christo
  • 354
  • 2
  • 13

2 Answers2

3

useMemo - You can store the memoized version directly on the instance, along with any information that you want to use to invalidate and recalculate that information. See What about memoization? in the React You Probably Don't Need Derived State article.

useCallback - You typically don't need to create callbacks when using a class component, you make them methods of the class (perhaps binding them in the constructor) or assign arrow functions to properties in the constructor, so the need for useCallback doesn't arise. Those methods can use this.state, this.setState, and when setting new state based on existing state, the callback form of this.setState. useCallback is more helpful in function components because typically the callbacks are created within the function, and you don't want to use a new copy on every render.

Here's an example using a very quick-and-dirty memoization helper (you may want a library instead, as described in the article linked above). Notice how derivedValue is only updated when value1 or value2 change, not when value3 changes. Also note that the click handler is stable (doesn't change on each render), so we only see Button render once.

// VERY BASIC shallow check to see if the dependencies changed
function depsChanged(oldDeps, newDeps) {
    return !oldDeps || oldDeps.length !== newDeps.length || oldDeps.some((dep, index) => dep !== newDeps[index]);
}

// PureComponent nly re-renders when props change, so it's a handy way to check that
// our click handler doesn't change unnecessarily
class Button extends React.PureComponent {
    render() {
        const { clickHandler, text } = this.props;
        console.log(`Button rendering`);
        return <input type="button" onClick={clickHandler} value={text} />;
    }
}

class Example extends React.Component {
    // A value we derive from props
    derivedValue: 0;

    // The dependencies we used when calculating `value` in `render`
    valueDeps = null;

    constructor(props) {
        super(props);

        // Handy so `this` is always the component instance when `clickHandler` is called
        this.clickHandler = this.clickHandler.bind(this);
    }

    clickHandler() {
        console.log(`derivedValue = ${this.derivedValue}`);
    }

    render() {
        const { value1, value2, value3 } = this.props;

        // Do we need to recreate `derivedValue`?
        const newDeps = [value1, value2]
        if (depsChanged(this.valueDeps, newDeps)) {
            // Yes
            console.log(`Recalculating derivedValue from ${value1} and ${value2}`);
            this.valueDeps = newDeps;
            this.derivedValue = value1 + value2; // Stand-in for the complex operation
        }

        return <div>
            <div>value1 = {value1}</div>
            <div>value2 = {value2}</div>
            <div>value3 = {value3}</div>
            <div>derivedValue = {this.derivedValue}</div>
            <Button clickHandler={this.clickHandler} text="Click Me" />
        </div>;
    }
}

class App extends React.Component {
    state = {
        value1: 0,
        value2: 0,
        value3: 0,
    };

    constructor(props) {
        super(props);
        // Another way to do callbacks. I prefer to use the method syntax above, but
        // people also do this.
        this.incVaue1 = () => this.setState(({value1}) => ({value1: value1 + 1}));
        this.incVaue2 = () => this.setState(({value2}) => ({value2: value2 + 1}));
        this.incVaue3 = () => this.setState(({value3}) => ({value3: value3 + 3}));
    }

    render() {
        const { value1, value2, value3 } = this.state;
        const { incVaue1, incVaue2, incVaue3 } = this;

        return <div>
            <Example value1={value1} value2={value2} value3={value3} />
            <div>
                <input type="button" onClick={incVaue1} value="Increment value1" />
                <input type="button" onClick={incVaue2} value="Increment value2" />
                <input type="button" onClick={incVaue3} value="Increment value3" />
            </div>
        </div>;
    }
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

React doesn't come with a built in way to do memoization for class components, but you can use an external memoization library, or create your own if you want. For example, using memoize-one:

import memoizeOne from 'memoize-one';

class Example extends React.Component {

  expensiveCalculation = memoizeOne((someValue) => {
    // ...
  });

  render() {
    const result = this.expensiveCalculation(this.props.someProp);

    // ...
  }
}

how can I reproduce [...] useCallback

Callbacks don't usually need to be memoized in class components. If your callback is simply a method on the class, it will automatically be a stable reference between renders.

class Example extends React.Component {
  onClick = () => {

  }

  render() {
    return <div onClick={this.onClick} />
  }
}
Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98