Select your region
and interface language
We’ll show relevant
Telegram channels and features
Region
avatar

Заметки про React

ru_react_notes
Короткие заметки про React.js, TypeScript и все что с ним связано
Subscribers
3 710
24 hours
-3
30 days
3
Post views
2 671
ER
71,99%
Posts (30d)
Characters in post
1 673
Insights from AI analysis of channel posts
Channel category
Technology and Apps
Audience gender
Male
Audience age
25-34
Audience financial status
Middle
Audience professions
Technology & Software Development
Summary
June 24, 09:15
Media unavailable
1
Show in Telegram

Реактивность – это легко
Стандартный useContext заставляет обновляться все компоненты-потребители, даже если им не нужна изменившаяся часть состояния. Это приводит к лишним ре-рендерам, особенно в больших компонентах вроде таблиц или списков.
Автор статьи столкнулся с этим в MUI X Data Grid: клик по одной ячейке вызывал ре-рендер всех остальных. Решение — точечная подписка через селекторы
Вместо того, чтобы хранить состояние в useState и передавать его через Context, можно использовать внешний Store и специальный хук useSelector. Идея проста:
🔴
Store — это обычный класс, который хранит состояние, умеет подписывать на обновления (subscribe) и уведомлять подписчиков, когда данные изменились (update). Он живёт вне рендера React.
🔴
useSelector(store, selectorFn)
— кастомный хук, который принимает store и функцию-селектор. Селектор — это функция, которая из всего объекта состояния достает только нужный компонентa фрагмент данных (например, `state => state.focus === index`).
Хук подписывается на store и вызывает локальный ре-рендер только тогда, когда возвращаемое селектором значение изменилось.
Пример кода:
const Context = createContext();
export function Grid() {
const [store] = useState(() => new Store({ focus: 0 }));
return (

{Array.from({ length: 50 }).map((_, i) => (

))}

);
}
const selectors = {
isFocus: (state, index) => state.focus === index,
};
function Cell({ index }) {
const store = useContext(Context);
const focus = useSelector(store, selectors.isFocus, index);
return (
ref={ref}
onClick={() => store.update({ ...store.state, focus: index })}
className={clsx({ focus })}
>
{index}

);
};
Такой подход позволяет обновлять только те компоненты, чьи данные действительно изменились, и часто избавляет от необходимости оборачивать всё в React.memo.
https://romgrk.com/posts/reactivity-is-easy/

June 17, 09:20

Остерегайтесь скрытых проблем при работе с search params
Типо-безопасность — это только вершина айсберга, о которой вспоминают разработчики при работе с search params. Автор библиотеки nuqs предупреждает о скрытых проблемах при работе с search params.
Запись и чтение. Для получения типо-безопасного стейта search params необходимо внедрять библиотеки валидации (например Zod). Для записи в search params сложных стейтов, а потом для их чтения обратно, понадобятся функции сериализации и парсинга соответственно. В nuqs из коробки есть встроенные парсеры для основных типов данных.
Помимо типо-безопасности, при чтении search params важно иметь runtime-безопасность. Даже после парсинга в корректный тип данных, это значение может быть невалидным. Например, значение валидно если находится в диапазоне -90/+90 или -180/+180. Или email написан правильно. Поэтому после парсинга должна происходить валидация данных. Аналогично, перед сериализацией должна происходить валидация, чтобы в URL не попали невалидные значения.
Еще одна из скрытых проблем при работе с URL – частота обновления URL. У разных браузеров есть разный лимит на частоту обновлений URL (в Chrome это 50мс). Проблема может возникнуть из-за привязки URL к высокочастотному инпуту, например

или

.
Со временем схема стейта в URL может изменяться, т.е. могут переименовываться поля, удаляться или добавляться новые. Нужно поддержать возможность миграции на новую схему.
При изменении URL может происходить замена текущей истории или добавлении в историю нового URL. При добавлении URL в историю появляется возможность управлять историей через браузерные кнопки вперед/назад. Это удобно для пользователя, но добавляет сложность для разработки, т.к. появляется два источника управления историей: через UI и браузерные кнопки вперед/назад. Например, при открытии модального окна добавляется в историю
?modalOpen=true
. Теперь пользователь ожидает, что при нажатии на браузерную кнопку назад закроет окно.
https://nuqs.47ng.com/blog/beware-the-url-type-safety-iceberg

June 04, 09:20

Почему Error Boundary, а не просто try/catch для компонентов
В React нельзя использовать try/catch чтобы отловить ошибки рендера компонента. Это связано с тем, что React не вызывает функцию Calculator когда он создает элемент, он лишь создает описание того что надо отрендерить.
const element = (


Calculator





)
Поэтому если обернуть объявление выше в try/catch можно получить только ошибки во время создания этих элементов, а не ошибки рендера. Реальные ошибки происходят внутри компонента, во время рендера, эффектов и обработчиков ошибок.
function Calculator(props) {
try {
// ...render logic
} catch (error) {
return
Ошибка!

}
}
По примеру выше можно обернуть в try/catch тело компонента, но это лишь обработает ошибки на уровне данного компонента.
Для обработки ошибок внутри компонента используют Error Boundary. Рекомендуется использовать библиотеку react-error-boundary, которая предоставляет готовый компонент ErrorBoundary и хук useErrorBoundary для обработки ошибок в асинхронных колбеках, эффектах, обработчиках ошибок. Пример использования хука:
import { useErrorBoundary } from 'react-error-boundary'
function MyComponent() {
const { showBoundary } = useErrorBoundary()
async function handleClick() {
try {
await doSomethingAsync()
} catch (error) {
showBoundary(error)
}
}
return
}
https://www.epicreact.dev/why-react-error-boundaries-arent-just-try-catch-for-components-i6e2l

May 29, 09:25

Анонс TypeScript Native
Вышла превью версия TypeScript написанная на Go. Ее можно установить через npm:
npm install -D @typescript/native-preview
Этот пакет предоставляет команду tsgo – он работает аналогично команде tsc. Со временем команда tsgo переименуется в tsc и переедет в пакет typescript. Сейчас команды разделены для удобства тестирования.
Помимо команды tsgo появилось расширение в VS Code для использования TypeScript Language Service на Go в редакторе – “TypeScript (Native Preview)”. С этим расширением должны ускориться такие функции, как go-to-definition, автокомоплит подсказок, вывод ошибок, показ всплывающих подсказок и другое.
https://devblogs.microsoft.com/typescript/announcing-typescript-native-previews/

May 27, 09:34

Управление фокусом в React с помощью flushSync
Установить фокус на инпуте сразу в React может оказаться не так просто, как кажется. При изменении стейта, React не сразу ре-рендерит компонент, а вместо этого он складывает в очередь все изменения состояний и выполняет их разом после завершения работы обработчика событий. Из-за такого поведения могут возникнуть непредвиденные ситуации, например:
function MyComponent() {
const inputRef = useRef(null)
const [show, setShow] = useState(false)
return (


onClick={() => {
setShow(true)
inputRef.current?.focus() // Фокус не будет работать
}}
>
Show

{show ? : null}

)
}
В примере выше фокс на инпуте не появится, из-за того что изменение стейта setShow сработает только по завершению работы обработчика события onClick.
Для решения проблемы используйте функцию flushSync, которая синхронно выполняет изменение стейта в переданном колбеке. По завершению работы flushSync DOM будет обновлен и
inputRef.current?.focus()
сработает. Обновленный пример обработчика:

onClick={() => {
flushSync(() => {
setShow(true)
})
inputRef.current?.focus()
}}
>

https://www.epicreact.dev/mastering-focus-management-in-react-with-flush-sync-f5b38

May 15, 09:32

TanStack DB
TanStack представила новую библиотеку – TanStack DB – реактивный клиентский стор, с возможностью синка API запросов. Библиотека работает поверх TanStack Query и расширяет его функционал. Разработчики обещают быструю скорость работы библиотеки, даже на больших объемах данных.
Библиотека предлагает fine-grained реактивность, возможность нормализации данных и примитивы транзакций.
Примеры использования:
Синк данных коллекции с API:
import { createQueryCollection } from "@tanstack/db-collections"
const todoCollection = createQueryCollection({
queryKey: ["todos"],
queryFn: async () => fetch("/api/todos"),
getId: (item) => item.id,
schema: todoSchema, // любая схема
})
Использование live query и фильтрации:
import { useLiveQuery } from "@tanstack/react-db"
const Todos = () => {
const { data: todos } = useLiveQuery((query) =>
query.from({ todoCollection }).where("@completed", "=", false)
)
return
}
Использование транзакций и оптимистичных изменений на клиенте:
import { useOptimisticMutation } from "@tanstack/react-db"
const AddTodo = () => {
const addTodo = useOptimisticMutation({
mutationFn: async ({ transaction }) => {
const { collection, modified: newTodo } = transaction.mutations[0]!
await api.todos.create(newTodo)
await collection.invalidate()
},
})
return (
onClick={() =>
addTodo.mutate(() =>
todoCollection.insert({
id: uuid(),
text: "
🔥
Make app faster",
completed: false,
})
)
}
/>
)
}
https://github.com/TanStack/db

May 13, 09:35
Media unavailable
1
Show in Telegram

Родители и владельцы в React: провайдеры контекста
Джулс Блом в своем блоге написала про то, что понимание того, как компоненты-родители (parent) и компоненты-владельцы (owner) влияют на обновление контекста, может помочь писать более производительные провайдеры контекста.
Чтобы провайдеры контекста со стейтом были производительными, надо чтобы провайдер с логикой изменения стейта был отдельным компонентом и принимал children. Пример:
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (

{children}

);
}
function App() {
return (





)
}
Чтобы разобраться, почему провайдер контекста в этом примере производительный, сначала стоит разобраться что такое компоненты-родители и компоненты-владельцы:
🔴
компонент-родитель содержит дочерние компоненты и определяет структурные отношения (вложенность) между компонентами, например в App компонент CounterProvider является родителем для вложенных в него компонентов;
🔴
компонент-владелец рендерит другие компоненты и определяет функциональные отношения, вызывает рендеринг, например это компонент CounterProvider или App.
Если вспомнить причины ре-рендеринга компонента, а это изменение стейта, обновление контекста и ре-рендер компонента-владельца, то становится понятно почему провайдер контекста стал производительнее. В CounterProvider логика со стейтом и при ее изменении будет происходить ре-рендер компонента CounterProvider и компонентов, которые подписаны на изменения контекста. При этом, компонент App не будет ре-рендерится, т.к. в нем нет изменений стейта.
https://julesblom.com/writing/parent-owners-context

April 29, 09:45

React Labs: View Transitions, Activity, и другое
В React Labs представили два новых экспериментальных API: View Transitions и Activity. Подробнее о каждой из них:
View Transitions предназначен для анимации переходов в приложении. С ее помощью можно декларативно объявить, что анимировать, когда и как. Пример:
// «Что» анимировать

animate me


Чтобы определить «когда» анимировать, можно использовать один из трех триггеров: startTransition, useDeferredValue или Suspense.
Для стилизации анимации используйте CSS:
// «Как» анимировать
::view-transition-old(*) {
animation: 300ms ease-out fade-out;
}
::view-transition-new(*) {
animation: 300ms ease-in fade-in;
}
Также есть возможность указывать причину анимации через API addTransitionType, пример:
function navigate(url) {
startTransition(() => {
addTransitionType('nav-forward');
go(url);
});
}
function navigateBack(url) {
startTransition(() => {
addTransitionType('nav-back');
go(url);
});
}
name="nav"
share={{
'nav-forward': 'slide-forward',
'nav-back': 'slide-back',
}}>
{heading}

::view-transition-old(.slide-forward) {
animation: ...
}
::view-transition-new(.slide-forward) {
animation: ...
}
Activity это новое API, которое позволяет визуально скрывать компонент с UI, снижать его приоритет при рендере, при этом сохраняя его стейт. Использование этого API дешевле в плане производительности по сравнению с размонтированием или скрытием через CSS. Пример компонента :



Когда у Activity мод 'visible', то компонент рендерится как обычно, а когда 'hidden', то размонтируется, но сохранит стейт и продолжит рендерится с более низким приоритетом чем видимые компоненты.
Acitivity и View Transition можно использовать вместе, пример:








https://react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more

April 22, 09:50

Показ тостов через React Server Components
В блоге Build UI показали пример, как, используя React Server Components, показывать всплывающие сообщения (тосты) на клиенте. В примере используются серверные функции, куки и useOptimistic. По итогу, чтобы вызвать тост на клиенте, достаточно в любой серверной функции сделать вызов функции
toast
, пример:
"use server";
export async function save() {
await toast("Blog post successfully saved!");
}
Функция toast выглядит следующим образом:
"use server";
import { cookies } from "next/headers";
async function toast(message: string) {
const cookieStore = await cookies();
const id = crypto.randomUUID();
cookieStore.set(`toast-${id}`, message, {
path: "/",
maxAge: 60 * 60 * 24, // 1 day
});
}
Используется случайный ID в названии куки для того, чтобы гарантировать уникальность названия куки.
Чтобы отображать куки на клиенте, надо считать куки, отфильтровать по префиксу
toast
:
import { cookies } from "next/headers";
import { ClientToasts } from "./client-toasts";
export async function Toaster() {
const cookieStore = await cookies();
const toasts = cookieStore
.getAll()
.filter((cookie) => cookie.name.startsWith("toast-") && cookie.value)
.map((cookie) => ({
id: cookie.name,
message: cookie.value,
dismiss: async () => {
"use server";
const cookieStore = await cookies();
cookieStore.delete(cookie.name);
},
}));
return ;
}
Как видно из кода выше, для удаления куки используется серверный API Next.js. Почему не использовать браузерное API для удаления? По мнению автора, работать с браузерным
document.cookie
менее приятно, чем с API от Next.js. В ClientToasts автор использует хук useOptimistic, чтобы реализовать немедленное удаление тоста с экрана пользователя и отправить запрос на удаление куки на сервер:
export function ClientToasts({ toasts }: { toasts: Toast[] }) {
const [optimisticToasts, remove] = useOptimistic(toasts, (current, id) =>
current.filter((toast) => toast.id !== id),
);
const localToasts = optimisticToasts.map((toast) => ({
...toast,
dismiss: async () => {
remove(toast.id);
await toast.dismiss();
},
}));
return …
}
Одна из особенностей подхода хранения тостов в куках заключается в том, что тосты будут отображаться при редиректах, в новых вкладках и перезагрузках страницы.
https://buildui.com/posts/toast-messages-in-react-server-components

April 17, 09:55

Кейсы оптимизации React
Обзор примеров, показывающих, как пять разных команд разработчиков оптимизировали React. Например, у одной из команд на проекте был плохой показатель метрики INP, которая отслеживает скорость отклика сайта. Хорошим считается отклик менее 200мс, на проекте был отклик 380мс. Какие были найдены причины:
- Большой бандл. Первоначально грузился большой бандл с кодом, который не был нужен. Помог code splitting.
- Определенные колбеки у обработчиков событий вызывались слишком часто, из-за чего блокировался основной поток браузера. Помог debounce на скролл и resize, а также делегирование событий.
- Ре-рендерелись больше компонентов чем нужно при изменении стейта. Помогло оборачивание компонентов в
React.memo()
и использование useMemo для дорогих вычислений.
- Долгий рендеринг компонентов. Помогло обновление до React 18. Основным преимуществом этого обновления стал параллельный рендеринг, поскольку высокоприоритетные задачи, такие как пользовательский клик, могут прерывать рендеринг. Это обновление улучшило метрику INP на 46% на десктопе.
Помимо это помогли следующие исправления для улучшения производительности рендеринга:
- Разбиение сложных хуков на более мелкие. На графике перфоманса было видно, что сложные хуки вызываются несколько раз и перерисовывают компонент без необходимости.
- Замена React Router на window.location там, где это возможно.
- Улучшение мемоизации Redux селекторов.
https://largeapps.dev/case-studies/advanced/