Languages
[Edit]
EN

React - memo on event props (on event functions)

9 points
Created by:
Frida-Timms
667

In this article, we would like to show how to use memo on event props (on event functions) in React.

Motivation

When functions references change in component props the component gets re-rendering. It happens also when memo() is used, even the method body doesn't change, because of new function creation. We can solve the problem by using useCallback() or useMemo() on that functions but it complicates source code.

React doesn't provide memo hook that prevents against re-rendering only on selected event props. It is easy to create own one hook, what was shown in this article.

Solution

In this article we propose simple way how to create memo on selected props that have functions as values by adding $ prefix to props names.

Practical example

App.jsx file:

import React, {useState} from 'react';
import pipe from './pipe';

const NormalInput = ({value, $onChange}) => {  // <------------------------------------- $ makes memo on event props
    console.log('MyInput rendering!');
    return (
        <input value={value} onChange={$onChange} />
    );
};

const PipedInput = pipe(NormalInput);  // <--------------------------------------------- enables memo on event props

const App = () => {
    console.log('App rendering!');
    const [count, setCount] = useState(0);
    const [value, setValue] = useState('');
    const handleChange = (e) => {
        setValue(e.currentTarget.value);
    };
    return (
        <div>
          <div>
            <span>Count: {count}</span>
            <button onClick={() => setCount(value => value + 1)}>Increment</button>
          </div>
          <div>
            <PipedInput value={value} $onChange={handleChange} />{/* <------------------ $ makes memo on event props */}
            <button onClick={() => setValue('text-1')}>Set: text-1</button>
            <button onClick={() => setValue('text-2')}>Set: text-2</button>
          </div>
        </div>
    );
};

export default App;

 

pipe.jsx file:

import React, {useState, useMemo, createElement} from 'react';

const createWrapper = (state, props) => {
    const properties = {};
    const dependencies = [];
    for (const key in props) {
        const value = props[key];
        if (key[0] === '$') {
            const code = key.substring(1);
            if (code in props) {
                throw new Error(`Property name is duplicated (as '${key}' and '${code}').`);
            }
            if (value == null) {  // null or undefined
                return;
            }
            if (typeof value === 'function') {
                let pipe = state[code];
                if (pipe) {
                    pipe.method = value;
                } else {
                    pipe = state[code] = {
                        method: value,
                        proxy: (...args) => {
                            const method = pipe.method;
                            if (method) {
                                return method(...args);
                            }
                            return undefined;
                        }
                    };
                }
                dependencies.push(pipe.proxy);
                properties[code] = pipe.proxy;
            } else {
                throw new Error('Incorrect function property name prefix usage.');
            }
        } else {
            if (key in state) {
                delete state[key];
            }
            dependencies.push(value);
            properties[key] = value;
        }
    }
    return {properties, dependencies};
};

const useWrapper = (props) => {
    const state = useMemo(() => ({}), []);
    return createWrapper(state, props);
};

const pipe = (component) => {
    //HINT: it is good to modify source code by-self and add forward ref support when needed.
    const Wrapper = (props) => {
        const {properties, dependencies} = useWrapper(props);
        return useMemo(() => createElement(component, properties), dependencies);
    };
    Wrapper.displayName = `Pipe(${component.displayName || component.name})`;
    Wrapper.defaultProps = component.defaultProps;
    return Wrapper;
};

export default pipe;

 

Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join