Пейджер

🌍 Привет мир! 👋

🌍 Привет мир! 👋

Хочется чтобы весь код работал с первого раза и более того, чтобы в нем вообще не было багов, но мир далеко не идеален, и сегодня в разборе проблема которую недавно я обнаружил в одном из проектов.

📊 Cтатистика

Мне стало интересно, сколько же багов создает средний разработчик, понятно что здесь миллион факторов которые влияют на значения, но все же ➕➖ что-то можно посчитать.

💡 Я нашел вот такую статистику, которую предоставила Coralogix компания.

❗ В среднем разработчик создает 70 ошибок на каждые 1000 строк кода.
❗ 15 ошибок на 1000 строк кода доходят до пользователей. Исправление ошибки занимает в 30 раз больше времени, чем написание одной строки кода.
❗ 75% времени разработчика уходит на отладку (1500 часов в год!).
❗ Только в США ежегодно тратится 113 миллиардов долларов на выявление и исправление дефектов продуктов.

💻 Проблема найденная в коде

Есть обычный функциональный компонент, в реальной проблеме он был немного больше и запутаннее, я оставил только самый сок.

const TodoComponent = ({ initialTasks = [] }) => {
    const [tasks, setTasks] = useState();

    useEffect(() => {
        setTasks({
            taskCount: initialTasks.length,
        });
    }, [initialTasks]);

    const renderTaskCount = tasks?.taskCount ?? 0;
    return (
        <>
            <h1>TodoComponent</h1>
            <p>Task Count: {renderTaskCount}</p>
        </>
    );
};


🧠 Подумайте в чем здесь может быть проблема, получится ли у вас сразу спрогнозировать проблему?

Ответ: Компонент будет уходить в Infinite Render Loop из-за того, что каждый раз создается initialTasks с пустым массивом.

👀 На первый взгляд проблема будет не всем очевидна. При обычном сценарии компонент может вызываться в одном месте и в этом случае передается initialTasks.

<TodoComponent initialTasks={[1, 2, 3]} />


Но что если появилась задача имплементировать компонент в другом месте❓ Вы без всяких сложностей, добавляете новый функционал, но не передаете пропс initialTasks, так как в нем нет необходимости.

<TodoComponent />


👇 В таком случае вы получаете:

Warning: Maximum update depth exceeded. 
This can happen when a component calls setState inside useEffect, 
but useEffect either doesn't have a dependency array, 
or one of the dependencies changes on every render


🔎 Проблема заключается в том, что когда родительский компонент вызывает TodoComponent и не передает initialTasks, то значение по умолчанию = [] каждый раз создается заново.
Это означает, что, хотя значение initialTasks одно и то же (пустой массив), это новая ссылка при каждом рендеринге. И все бы ничего, но initialTasks идет как dependencies в useEffect.

🛠️ Как итог каждый раз обновляется состояние setTasks, которое приводит к re-render компонента и создании нового массива → Infinite Render Loop.

❗ Если такая проблема не попадает на глаз сразу, то в дальнейшем это может привести к тяжелым поискам и временным затратам.

📝 Решения

1️⃣ Использовать внешнюю константу.

const defaultInitialTasks = [];

const TodoComponent = ({ initialTasks = defaultInitialTasks }) => ...


2️⃣ Использовать мемоизацию. Это гарантия того, что значение по умолчанию для initialTasks не изменится между рендерами, если оно явно не изменено.

    const defaultInitialTasks = useMemo(() => initialTasks || [], [initialTasks]);

    useEffect(() => {
        setTasks({
            taskCount: defaultInitialTasks.length,
        });
    }, [defaultInitialTasks]);


🙂 Всем хорошей недели и чистого кода!

💬 Делитесь своим мнением в комментариях👇! Если вам понравилась статья, не забудьте поставить лайк! 👍

#REACT
Медиа 1
Хотите больше таких постов?
Подпишитесь на канал и читайте продолжение в Telegram.
Подписаться на @ivanchikovitclub Открыть пост в Telegram