Skip to main content

3 posts tagged with "react"

View All Tags

Event Propagation in React

· 8 min read
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

When mixing React's event handler method with vanilla Javascript's event handler method, event propagation may not work as intended. For example, in a structure like button1, button2, clicking button2 can trigger the event handler of button1 as well.

const buttonEl = document.getElementById('button1');
buttonEl.addEventListener('click', () => {
console.log('button1 clicked');
});

const handleClick = (e) => {
e.stopPropagation();
console.log('button2 clicked');
};

return (
<button id='button1'>
<button id='button2' onClick={handleClick}>Click me</button>
</button>
);
// When button2 is clicked
button2 clicked
button1 clicked

Solution

Event propagation in React differs from that in vanilla Javascript because React handles event propagation using a delegation method. (comment)

In React (post React 17), event listeners are registered on the rootDOM. Therefore, event listeners within React propagate as expected according to the DOM structure, but vanilla Javascript event propagation may not behave as anticipated.

While it's best to avoid mixing the two types of events, sometimes it's unavoidable when using third-party libraries or handling parts of the code you can't control. In such cases, you must block event propagation according to the behavior of each method.

// Unify event listener to vanilla Javascript
const buttonEl = document.getElementById('button1');
buttonEl.addEventListener('click', () => {
console.log('button1 clicked');
});

const button2El = document.getElementById('button2');
button2El.addEventListener('click', (e) => {
e.stopPropagation();
console.log('button2 clicked');
});

return (
<button id='button1'>
<button id='button2'>Click me</button>
</button>
);

// Or
// Unify event listener to React
const handleClick1 = (e) => {
console.log('button1 clicked');
};

const handleClick = (e) => {
e.stopPropagation();
console.log('button2 clicked');
};

return (
<button id='button1' onClick={handleClick1}>
<button id='button2' onClick={handleClick}>Click me</button>
</button>
);

Reasons to Avoid Nested Components

· 4 min read
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

During a project, I encountered an issue where unnecessary re-renders were occurring.

The problem was not just rendering, but the fact that the DOM was being completely redrawn with each render. The culprit was the use of nested components in the code.

In this article, I will explain why you should avoid using nested components.

Cheat Sheet

// Bad
const List = ({ hasWrapper, borderStyle, children }) => {
const Border = () => {
return <p style={borderStyle}>{children}</p>;
}

if (hasWrapper) {
return (
<Wrapper>
<Border />
</Wrapper>
);
}

return <Border/>;
};
// Good
const List = ({ hasWrapper, borderStyle, children }) => {
if (hasWrapper) {
return (
<Wrapper>
<Border borderStyle={borderStyle}>
{children}
</Border>
</Wrapper>
);
}

return (
<Border borderStyle={borderStyle}>
{children}
</Border>
);
};

const Border = ({ borderStyle, children }) => {
return <p style={borderStyle}>{children}</p>;
}

As shown above, you might use a nested component to reuse the code for Border and the props of List. However, in such cases, the children are unnecessarily repaint in the DOM with each render.

How to Avoid Re-rendering Caused by Callback Function Props

· 7 min read
Hyunmo Ahn
Front End Engineer @ Line+

In React, when passing a function as component props, one needs to be careful with managing references to avoid unnecessary re-renders. Although most case we use useCallback to prevent unnecessary re-renders, I found an alternative solution sharing.

The code that inspired this article is radix-ui/primitives's useCallbackRef. This article will discuss the use cases and logic of useCallbackRef.

Below is the useCallbackRef code we are going to discuss. Can you guess in what situations it might be used?

// useCallbackRef.js
import { useRef, useEffect, useMemo } from 'react';

export function useCallbackRef(callback) {
const callbackRef = useRef(callback);

useEffect(() => {
callbackRef.current = callback;
});

return useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
}