EN
React - memo on event props (on event functions)
9 points
In this article, we would like to show how to use memo on event props (on event functions) in React.
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.
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.
App.jsx
file:
xxxxxxxxxx
1
import React, {useState} from 'react';
2
import pipe from './pipe';
3
4
const NormalInput = ({value, $onChange}) => { // <------------------------------------- $ makes memo on event props
5
console.log('MyInput rendering!');
6
return (
7
<input value={value} onChange={$onChange} />
8
);
9
};
10
11
const PipedInput = pipe(NormalInput); // <--------------------------------------------- enables memo on event props
12
13
const App = () => {
14
console.log('App rendering!');
15
const [count, setCount] = useState(0);
16
const [value, setValue] = useState('');
17
const handleChange = (e) => {
18
setValue(e.currentTarget.value);
19
};
20
return (
21
<div>
22
<div>
23
<span>Count: {count}</span>
24
<button onClick={() => setCount(value => value + 1)}>Increment</button>
25
</div>
26
<div>
27
<PipedInput value={value} $onChange={handleChange} />{/* <------------------ $ makes memo on event props */}
28
<button onClick={() => setValue('text-1')}>Set: text-1</button>
29
<button onClick={() => setValue('text-2')}>Set: text-2</button>
30
</div>
31
</div>
32
);
33
};
34
35
export default App;
pipe.jsx
file:
xxxxxxxxxx
1
import React, {useState, useMemo, createElement} from 'react';
2
3
const createWrapper = (state, props) => {
4
const properties = {};
5
const dependencies = [];
6
for (const key in props) {
7
const value = props[key];
8
if (key[0] === '$') {
9
const code = key.substring(1);
10
if (code in props) {
11
throw new Error(`Property name is duplicated (as '${key}' and '${code}').`);
12
}
13
if (value == null) { // null or undefined
14
return;
15
}
16
if (typeof value === 'function') {
17
let pipe = state[code];
18
if (pipe) {
19
pipe.method = value;
20
} else {
21
pipe = state[code] = {
22
method: value,
23
proxy: (args) => {
24
const method = pipe.method;
25
if (method) {
26
return method(args);
27
}
28
return undefined;
29
}
30
};
31
}
32
dependencies.push(pipe.proxy);
33
properties[code] = pipe.proxy;
34
} else {
35
throw new Error('Incorrect function property name prefix usage.');
36
}
37
} else {
38
if (key in state) {
39
delete state[key];
40
}
41
dependencies.push(value);
42
properties[key] = value;
43
}
44
}
45
return {properties, dependencies};
46
};
47
48
const useWrapper = (props) => {
49
const state = useMemo(() => ({}), []);
50
return createWrapper(state, props);
51
};
52
53
const pipe = (component) => {
54
//HINT: it is good to modify source code by-self and add forward ref support when needed.
55
const Wrapper = (props) => {
56
const {properties, dependencies} = useWrapper(props);
57
return useMemo(() => createElement(component, properties), dependencies);
58
};
59
Wrapper.displayName = `Pipe(${component.displayName || component.name})`;
60
Wrapper.defaultProps = component.defaultProps;
61
return Wrapper;
62
};
63
64
export default pipe;