Рефакторинг запутанного компонента
В своем блоге Алекс Кондов рассказал, как бы он проводил рефакторинг запутанного компонента формы. Кратко, о чем он пишет:
- Написать тесты. Если еще тестов нет, то перед рефакторингом надо написать тесты для компонента. В тестах должна проверяться бизнес логика. Делается это для того, чтобы знать, что в процессе рефакторинга ничего не сломается.
- Использовать линтер. Нужно добавить автоматические проверки, которые подсветят проблемный код. Например, неиспользуемые импорты, значения стейта и т.д.
- Удалить «мертвый» код. Нужно удалить лишние комментарии, неиспользуемые переменные. В этом как раз поможет линтер.
- Сократить количество стейтов. Если в компоненте много useState, то скорее всего он делает слишком много разных вещей. В этом случае его можно разделить на подкомпоненты и связать через пропсы.
- Убрать большие условные рендеры. Если блоки похожи, то их можно объединить в один компонент. Если блоки различны, то лучше разделить их на два компонента и разделить логику между ними.
- Следить за ответственностью компонента, чтобы внутри него было как можно меньше действий. В дальнейшем это упростит понимание кода.
- Вынести утилитарные функции за пределы компонент. Функция может находиться в теле компонента, только если ей требуется доступ к стейту или другому значению внутри компонента.
- Используйте для валидации форм декларативные схемы, например Zod, Yup.
- Не используйте inline стили.
https://alexkondov.com/refactoring-a-messy-react-component/
В своем блоге Алекс Кондов рассказал, как бы он проводил рефакторинг запутанного компонента формы. Кратко, о чем он пишет:
- Написать тесты. Если еще тестов нет, то перед рефакторингом надо написать тесты для компонента. В тестах должна проверяться бизнес логика. Делается это для того, чтобы знать, что в процессе рефакторинга ничего не сломается.
- Использовать линтер. Нужно добавить автоматические проверки, которые подсветят проблемный код. Например, неиспользуемые импорты, значения стейта и т.д.
- Удалить «мертвый» код. Нужно удалить лишние комментарии, неиспользуемые переменные. В этом как раз поможет линтер.
- Сократить количество стейтов. Если в компоненте много useState, то скорее всего он делает слишком много разных вещей. В этом случае его можно разделить на подкомпоненты и связать через пропсы.
- Убрать большие условные рендеры. Если блоки похожи, то их можно объединить в один компонент. Если блоки различны, то лучше разделить их на два компонента и разделить логику между ними.
return !numberIncorrect ? (
// A lot of JSX...
) : (
// Even more JSX...
)
- Следить за ответственностью компонента, чтобы внутри него было как можно меньше действий. В дальнейшем это упростит понимание кода.
- Вынести утилитарные функции за пределы компонент. Функция может находиться в теле компонента, только если ей требуется доступ к стейту или другому значению внутри компонента.
- Используйте для валидации форм декларативные схемы, например Zod, Yup.
- Не используйте inline стили.
https://alexkondov.com/refactoring-a-messy-react-component/
Alexkondov
Common Sense Refactoring of a Messy React Component
One thing I’ve learned from all the consulting I’ve done is that rewrites rarely lead to anything good. Almost in all cases, when you have an application…
👍16🔥3❤1👎1
Чеклист безопасности React приложения
Обзор популярных уязвимостей фронтенд приложений в контексте React:
🔴 Cross-Site Scripting (XSS). Атака заключающаяся в внедрении вредоносного кода, который вызывается на странице у пользователя. Связано это с тем, что выводимые данные не обрабатывается должным образом. React хорошо защищен от XSS атак, однако все равно уязвим из-за неправильного использования функций, сторонних библиотек или нестандартных реализаций. При использовании dangerouslySetInnerHTML санитизируйте передаваемые данные, например, через библиотеку dompurify:
🔴 Content Security Policy (CSP) заголовок. Использование CSP заголовка защитит от большинства веб-атак, включая XSS. CSP заголовок позволяет указать какие ресурсы могут загружаться и исполняться браузером. Позволяет работать в Report-Only режиме, позволяя только собирать обнаруженные проблемы, не применяя правила. Пример:
🔴 Cross-Site Request Forgery (CSRF). При CSRF атаке злоумышленник обманом заставляет пользователя отправить запрос, который он не собирался делать. Обычно это делается путем внедрения вредоносных запросов на сайт или e-mail, с которым взаимодействует пользователь. Для противодействия CSRF атаке используются токены, которые генерируются на странице и передаются вместе с запросом. Пример:
🔴 Переменные окружения. Используйте env переменные и не храните их значения в коде в репозитории.
https://www.trevorlasn.com/blog/frontend-security-checklist
Обзор популярных уязвимостей фронтенд приложений в контексте React:
import DOMPurify from 'dompurify';
const comment = '<script>alert("XSS Attack!")</script>Great article!';
const UserComments = () => {
return (
<div>
<li dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment) }} />
</div>
);
};
header Content-Security-Policy "script-src 'self' 'unsafe-inline' https://maps.googleapis.com https://www.googletagmanager.com;"
function CSRFProtectedForm() {
const [csrfToken, setCsrfToken] = useState('');
{/* Запрос CSRF токена */}
return (
<form onSubmit={submitForm}>
<input type="hidden" name="_csrf" value={csrfToken} />
{/* --snip-- */}
<button type="submit">Submit</button>
</form>
);
}
https://www.trevorlasn.com/blog/frontend-security-checklist
Please open Telegram to view this post
VIEW IN TELEGRAM
Google for Developers
Google Maps Platform | Google for Developers
Millions of websites and apps use Google Maps Platform to power location experiences for their users.
👍23❤2👎2
Глубокое погружение в формы в современном React
В своем блоге Кент С. Доддс рассказал, как использовать новые хуки useFormStatus, useActionState и useOptimistic:
🌟 useFormStatus. Если вы знакомы с контекстом в React, то представьте, что тег form - это провайдер данных, а useFormStatus - это хук, который имеет доступ к данным провайдера. С помощью хука можно получить информацию о состоянии формы и переданных данных. Минус хука в том, что, чтобы его использовать, нужно создавать отдельный компонент, который должен быть вложен внутри form.
🌟 useActionState. Этот хук позволяет обновить стейт по данным из формы. Разберем пример:
Хук принимает функцию joinEvent, начальный стейт
https://www.epicreact.dev/react-forms
В своем блоге Кент С. Доддс рассказал, как использовать новые хуки useFormStatus, useActionState и useOptimistic:
function JoinButton({ children }: { children: React.ReactNode }) {
const { pending, data } = useFormStatus()
return (
<button type="submit">
{pending ? `Joining as ${data.get('username')}...` : children}
</button>
)
}
function JoinEventForm() {
/** --snip */
return (
<form action={joinEvent}>
<JoinButton>Join</JoinButton>
</form>
)
}
async function joinEvent(
previousState: { joined: boolean },
formData: FormData,
) {
/** --snip */
return { joined: true }
}
function JoinEventForm() {
const [state, formAction, isPending] = useActionState(
joinEvent,
{ joined: false },
JOIN_URL,
)
return (
<div>
{state.joined ? (
<p>See you there!</p>
) : (
<form action={formAction}>
<button type="submit">
{isPending ? 'Joining...' : 'Join'}
</button>
</form>
)}
</div>
)
}
Хук принимает функцию joinEvent, начальный стейт
{ joined: false } и опциональный параметр пермалинк JOIN_URL. Он возвращает массив с текущим стейтом, функцию триггера действия и флаг – находится ли форма в состоянии отправки. Обратите внимание, что функция joinEvent принимает первым аргументом предыдущее состояние. В каком-то смысле можно представить, что это редьюсер для useReducer. https://www.epicreact.dev/react-forms
Please open Telegram to view this post
VIEW IN TELEGRAM
Epic React
A deep dive on forms with modern React
React 19 introduces terrific primitives for building great forms. Let's dive deep on forms for the web with React.
👍14
Создание стейт менеджера за 40 строк
Пример простого стейт менеджера для React, работающего через хук useSyncExternalStore.
Хук useSyncExternalStore появился в React 18 и позволяет подписаться на изменение внешнего хранилища. Внешним хранилищем может быть что угодно, например, браузер.
Значения в внешнем хранилище могут меняться, а с помощью хука эти изменения можно отследить. Пример стейт менеджера:
Если хук useCountStore будет использоваться в нескольких компонентах, то его значение будет общим между всеми компонентами.
https://paripsky.github.io/blog/build-your-own-react-state-management/
Пример простого стейт менеджера для React, работающего через хук useSyncExternalStore.
Хук useSyncExternalStore появился в React 18 и позволяет подписаться на изменение внешнего хранилища. Внешним хранилищем может быть что угодно, например, браузер.
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Значения в внешнем хранилище могут меняться, а с помощью хука эти изменения можно отследить. Пример стейт менеджера:
import { useSyncExternalStore } from 'react';
export type Listener = () => void;
function createStore<T>({ initialState }: { initialState: T }) {
let subscribers: Listener[] = [];
let state = initialState;
const notifyStateChanged = () => {
subscribers.forEach((fn) => fn());
};
return {
subscribe(fn: Listener) {
subscribers.push(fn);
return () => {
subscribers = subscribers.filter((listener) => listener !== fn);
};
},
getSnapshot() {
return state;
},
setState(newState: T) {
state = newState;
notifyStateChanged();
},
};
}
export function createUseStore<T>(initialState: T) {
const store = createStore({ initialState });
return () => [useSyncExternalStore(store.subscribe, store.getSnapshot), store.setState] as const;
}
export const useCountStore = createUseStore(0);
function Counter() {
const [count, setCount] = useCountStore();
const increment = () => {
setCount(count + 1);
};
/* --snip-- */
}
Если хук useCountStore будет использоваться в нескольких компонентах, то его значение будет общим между всеми компонентами.
https://paripsky.github.io/blog/build-your-own-react-state-management/
paripsky.github.io
Build your own React state management library in under 40 lines of code
A guide on how to built a custom state management solution for react in under 40 lines of code with typescript support
👍17❤2
Google Translate может сломать React приложение
Браузерное расширение Google Translate может сломать React приложение. Это связано с тем, что, когда расширение активируется, он ищет узлы TextNode для перевода. Эти узлы заменяются на FontElement с переведенными строками внутри. Например:
Как видно из примера, меняется DOM структура элемента. Исходный TextNode размонтируется и заменяется новым FontElement с переведенным текстом внутри.
Какие могут возникнуть проблемы при активированном Google Translate:
🔴 Значения стейта useState на странице может не обновляться. Если расширение активировано, то оно размонтирует узел и заменяет DOM элемент. Однако размонтированный DOM узел остается в памяти приложения и изменения стейта происходят в нем.
🔴 Краш приложения:
NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
Это может произойти из-за условного рендера строки, например:
🔴 Неправильное значение в
Кроме Google Translate приложение могут сломать и другие расширения, которые манипулируют DOM. На данный момент нет реального решения этой проблемы. Есть лишь некоторые способы обхода, но они создают дополнительные проблемы. Например, один из более-менее простых способов обхода проблемы заключается в оборачивании условного рендера строки в span.
https://martijnhols.nl/gists/everything-about-google-translate-crashing-react
Браузерное расширение Google Translate может сломать React приложение. Это связано с тем, что, когда расширение активируется, он ищет узлы TextNode для перевода. Эти узлы заменяются на FontElement с переведенными строками внутри. Например:
<p>There are 4 lights!</p>
// После перевода:
<p><font>Er zijn 4 lampen!</font></p>
Как видно из примера, меняется DOM структура элемента. Исходный TextNode размонтируется и заменяется новым FontElement с переведенным текстом внутри.
Какие могут возникнуть проблемы при активированном Google Translate:
NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
Это может произойти из-за условного рендера строки, например:
{lightsOn && 'There are 4 lights!’}event.target. Когда Google Translate активируется, то event.target у текста становится элемент font, а не ожидаемый элемент, в котором был изначальный текст. Кроме Google Translate приложение могут сломать и другие расширения, которые манипулируют DOM. На данный момент нет реального решения этой проблемы. Есть лишь некоторые способы обхода, но они создают дополнительные проблемы. Например, один из более-менее простых способов обхода проблемы заключается в оборачивании условного рендера строки в span.
https://martijnhols.nl/gists/everything-about-google-translate-crashing-react
Please open Telegram to view this post
VIEW IN TELEGRAM
Martijn Hols
Everything about Google Translate crashing React (and other web apps) by Martijn Hols
A deep dive into Google Translate (and other browser extensions) interference breaking React and other web apps.
👍28❤1
Типобезопасный REST API вместе с Zod
При работе с REST API нельзя быть уверенным в том, что вернется в ответе запроса. Даже если используешь TypeScript и пишешь типы для ответа, то запрос все равно может вернуть другой ответ, в следствие чего может произойти runtime ошибка.
Если хотите быть уверенными в том, что возвращает REST API, то используйте валидацию ответа через Zod схемы. В этом случае, если ответ запроса не соответствует ожиданию, выбросится ошибка. Пример:
https://noahflk.com/blog/typesafe-rest-api
При работе с REST API нельзя быть уверенным в том, что вернется в ответе запроса. Даже если используешь TypeScript и пишешь типы для ответа, то запрос все равно может вернуть другой ответ, в следствие чего может произойти runtime ошибка.
Если хотите быть уверенными в том, что возвращает REST API, то используйте валидацию ответа через Zod схемы. В этом случае, если ответ запроса не соответствует ожиданию, выбросится ошибка. Пример:
const FetchedDataSchema = z.object({
id: z.number(),
name: z.string(),
});
type FetchedData = z.infer<typeof FetchedDataSchema>;
function DataFetchingComponent() {
const fetchData = async (): Promise<FetchedData> => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return FetchedDataSchema.parse(data);
};
/* --snip-- */
}
https://noahflk.com/blog/typesafe-rest-api
noahflk.com
Making a REST API typesafe with React Query and Zod - Noah Falk
Using React Query alongside TypeScript and Zod, we can achieve a type-safe, efficient data fetching process in React that ensures consistency
👍20❤2
Обработка ошибок в React Server Components
В серверных компонентах React могут возникать ошибки. Для их обработки можно использовать ErrorBoundary из react-error-boundary, который поддерживает восстановление ошибки и повторную попытку рендеринга.
С помощью RSC и react-error-boundary можно реализовать повторный рендеринг. Пример компонента ErrorFallback, который выполняет повторный рендеринг по клику на кнопку:
Как видно из примера, используется router.refresh() для повторного рендера компонента, в котором произошла ошибка. На самом деле ре-рендерится вся страница, но пользователю кажется, что ре-рендерится только компонент с ошибкой. Т.к. router.refresh() долгая операция, которая не возвращает Promise, то для отслеживания выполнения она вызывается внутри startTransition.
https://edspencer.net/2024/7/16/errors-and-retry-with-react-server-components
В серверных компонентах React могут возникать ошибки. Для их обработки можно использовать ErrorBoundary из react-error-boundary, который поддерживает восстановление ошибки и повторную попытку рендеринга.
С помощью RSC и react-error-boundary можно реализовать повторный рендеринг. Пример компонента ErrorFallback, который выполняет повторный рендеринг по клику на кнопку:
'use client'
import { startTransition, useState } from 'react'
import { useRouter } from 'next/navigation'
export default function ErrorFallback({
error,
resetErrorBoundary,
}: {
error: Error
resetErrorBoundary: () => void
}) {
const router = useRouter()
// Стейт кнопки сброса
const [isResetting, setIsResetting] = useState(false)
function retry() {
setIsResetting(true)
startTransition(() => {
router.refresh()
resetErrorBoundary()
setIsResetting(false)
})
}
return (
<button onClick={() => retry()}>
{isResetting ? <Spinner /> : null}
Retry
</button>
)
}
Как видно из примера, используется router.refresh() для повторного рендера компонента, в котором произошла ошибка. На самом деле ре-рендерится вся страница, но пользователю кажется, что ре-рендерится только компонент с ошибкой. Т.к. router.refresh() долгая операция, которая не возвращает Promise, то для отслеживания выполнения она вызывается внутри startTransition.
https://edspencer.net/2024/7/16/errors-and-retry-with-react-server-components
Ed Spencer's Blog
Error handling and retry with React Server Components
React Server Components can throw errors. Here's how to handle and recover from them
👎7👍4❤1🔥1
Серверные функции в Tanstack Router
В Tanstack Router появилась возможность использовать серверные функции.
Серверные функции можно вызывать на сервере или на клиенте. Под капотом используется библиотека Vinxi, предназначенная для создания фулстек приложений.
Серверные функции извлекаются из клиентского бандла в отдельный серверный бандл во время компиляции. На сервере они выполняются как есть и результат отправляется обратно клиенту. На клиенте вызов серверных функций проксируется на сервер, где выполняется функция и результат возвращается на клиент.
https://tanstack.com/router/latest/docs/framework/react/start/server-functions
В Tanstack Router появилась возможность использовать серверные функции.
// getServerTime.ts
import { createServerFn } from '@tanstack/start'
export const getServerTime = createServerFn('GET', async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return new Date().toISOString()
})
// Time.tsx
import { useServerFn } from '@tanstack/start'
import { useQuery } from '@tanstack/react-query'
import { getServerTime } from './getServerTime'
export function Time() {
const getTime = useServerFn(getServerTime)
const timeQuery = useQuery({
queryKey: 'time',
queryFn: () => getTime(),
})
}
Серверные функции можно вызывать на сервере или на клиенте. Под капотом используется библиотека Vinxi, предназначенная для создания фулстек приложений.
Серверные функции извлекаются из клиентского бандла в отдельный серверный бандл во время компиляции. На сервере они выполняются как есть и результат отправляется обратно клиенту. На клиенте вызов серверных функций проксируется на сервер, где выполняется функция и результат возвращается на клиент.
https://tanstack.com/router/latest/docs/framework/react/start/server-functions
Tanstack
Server Functions | TanStack Start React Docs
What are Server Functions? Server functions let you define server-only logic that can be called from anywhere in your application loaders, components, hooks, or other server functions. They run on the...
👍9
@svg-use для импорта SVG
Библиотека
Подход SVG-in-JS популярен и удобен для вставки SVG иконок на сайт. В этом подходе с помощью библиотеки svgr SVG преобразуется в React компонент. У этого подхода есть свои плюсы: возможность кастомизации иконок и удобство доставки SVG на сайт внутри JS бандла. Однако, у этого подхода есть и свои минусы:
🔴 Каждый SVG компонент сначала парсится в JS, потом в HTML, когда вставляется в дерево, это влияет на производительность.
🔴 Раздувание HTML дерева.
🔴 Увеличение размера загружаемого JS бандла.
Библиотека
В результате на месте использования компонента Arrow рендерится SVG:
В asset проекта генерируется файл icon-someHash.svg:
https://fotis.xyz/posts/introducing-svg-use/
Библиотека
@svg-use это набор инструментов и плагинов сборки для использования SVG с помощью механизма <use href>. Автор библиотеки хочет решить проблемы подхода SVG-in-JS с помощью <use href>, но при этом оставляя возможность кастомизации иконок. Подход SVG-in-JS популярен и удобен для вставки SVG иконок на сайт. В этом подходе с помощью библиотеки svgr SVG преобразуется в React компонент. У этого подхода есть свои плюсы: возможность кастомизации иконок и удобство доставки SVG на сайт внутри JS бандла. Однако, у этого подхода есть и свои минусы:
Библиотека
@svg-use встраивается в сборщик и генерирует SVG файлы как asset. Внутри сгенерированного SVG файла будут прописаны CSS переменные. Как выглядит использование библиотеки @svg-use:
import { Component as Arrow } from './icon.svg?svgUse';
return <Arrow color="red" />;
В результате на месте использования компонента Arrow рендерится SVG:
<svg viewBox="0 0 24 24">
<use
href="https://my-site.com/assets/icon-someHash.svg#use-href-target"
style="--svg-use-href-color: red"
></use>
</svg>
В asset проекта генерируется файл icon-someHash.svg:
<svg
xmlns="https://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="var(--svg-use-href-color, #111)"
id="use-href-target"
>
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
https://fotis.xyz/posts/introducing-svg-use/
Please open Telegram to view this post
VIEW IN TELEGRAM
Fotis Papadogeorgopoulos
Introducing @svg-use
Introducing @svg-use, a set of tools and bundler plugins, to ergonomically load SVG files as components, via SVG's use[href] mechanism.
👍24❤1
Namespace для компонентов
При организации компонентов в React используется подход, когда компонент разбивается на несколько частей и доступ к каждой части происходит через точку, например Card и Card.Header, Card.Body и Card.Footer.
Есть несколько способов реализовать такой подход, например через Function Assignment:
Либо через Object.assign:
У обоих этих подходов есть минусы:
🔴 Проблема с сериализацией, из-за чего этот подход не будет работать в React Server Components.
🔴 Проблема с tree shaking: все части компонента будут затянуты в бандл, даже если будет использоваться только одна из них.
Для решения этих проблем можно использовать современный способ через ES модули. Пример:
В этом способе нет runtime вычислений и код работает в React Server Components.
Дополнено: однако из-за особенностей работы бандлера, tree shaking все равно работать не будет.
https://ivicabatinic.from.hr/posts/multipart-namespace-components-addressing-rsc-and-dot-notation-issues
При организации компонентов в React используется подход, когда компонент разбивается на несколько частей и доступ к каждой части происходит через точку, например Card и Card.Header, Card.Body и Card.Footer.
Есть несколько способов реализовать такой подход, например через Function Assignment:
function Card() {}
Card.Header = function Header() {}
Card.Body = function Body() {}
Card.Footer = function Footer() {}
export default Card;
Либо через Object.assign:
export default Object.assign(Card, {
Header,
Body,
Footer,
});
У обоих этих подходов есть минусы:
Для решения этих проблем можно использовать современный способ через ES модули. Пример:
// card.tsx
export function CardRoot() {}
export function CardHeader() {}
export function CardBody() {}
export function CardFooter() {}
// namespaces.ts
export {
CardBody as Body,
CardRoot as Root,
CardFooter as Footer,
CardHeader as Header,
} from "./card";
// index.tsx
export * as Card from "./namespace";
// App.tsx
import { Card } from './src/ui/card';
// Card.Root, Card.Body и т.д.
В этом способе нет runtime вычислений и код работает в React Server Components.
Дополнено: однако из-за особенностей работы бандлера, tree shaking все равно работать не будет.
https://ivicabatinic.from.hr/posts/multipart-namespace-components-addressing-rsc-and-dot-notation-issues
Please open Telegram to view this post
VIEW IN TELEGRAM
ivicabatinic.from.hr
Multipart Namespace Components: Addressing RSC and Dot Notation Issues
A statically generated blog example using Next.js and Markdown.
👍40❤4
React и FormData
Для сбора данных формы можно использовать FormData – объект, который предоставляет геттеры и сеттеры для значений формы. Его можно использовать во время сабмита формы:
Чтобы избавиться от геттеров, можно использовать Object.fromEntries для представления данных в виде объекта:
Если вы работаете с TypeScript, то при использовании FormData могут возникнуть проблемы с типизацией. Эти проблемы можно решить с помощью схемы Zod и парсинга данных:
React 19 способствует использованию FormData и позволяет получать данные формы при сабмите через проп action:
https://reacttraining.com/blog/react-and-form-data
Для сбора данных формы можно использовать FormData – объект, который предоставляет геттеры и сеттеры для значений формы. Его можно использовать во время сабмита формы:
function onSubmit(event: React.FormEvent) {
event.preventDefault()
const formData = new FormData(event.target)
const formValues = {
name: formData.get('name')
email: formData.get('email)
}
}
Чтобы избавиться от геттеров, можно использовать Object.fromEntries для представления данных в виде объекта:
const formValues = Object.fromEntries(formData)
console.log(formValues) { name: 'my name', email: '[email protected]' }
typeof formValues // { [k: string]: FormDataEntryValue }
Если вы работаете с TypeScript, то при использовании FormData могут возникнуть проблемы с типизацией. Эти проблемы можно решить с помощью схемы Zod и парсинга данных:
const formValues = Object.fromEntries(formData) // ❌ TYPE: { [k: string]: FormDataEntryValue }
const results = myFormSchema.safeParse(formValues)
if (results.success) {
results.data // ✅ TYPE: { email: string, quantity: number }
console.log(results.data.email) // [email protected]
console.log(results.data.quantity) // 5
} else {
// Обработка ошибки
}
React 19 способствует использованию FormData и позволяет получать данные формы при сабмите через проп action:
function MyForm() {
function formAction(formData: FormData) {
// Получаем экземпляр formData вместо event
}
return <form action={formAction}>...</form>
}
https://reacttraining.com/blog/react-and-form-data
ReactTraining.com
React and FormData
React Corporate Workshops, Training, and Consulting
👍15👎1
Замена кода на React на CSS :has псевдокласс
Через CSS можно стилизовать дочерние элементы на основании родительского элемента:
С помощью псевдокласса :has можно сделать наоборот, снизу вверх: стилизовать родительский элемент на основании дочернего элемента. Например, карточки, в которых есть изображение, имеют другой цвет границы:
Псевдокласс :has поддерживается всеми основными браузерами, глобально 92% пользователей поддерживают его.
С помощью псевдокласса :has можно сделать многие фичи без написания кода на React. В своем блоге Надя Макаревич рассказала несколько примеров использования :has, которые заменяют код на React. Например, стилизация карточки и соседних карточек при фокусе на кнопки действий внутри карточки. Пример CSS стилей:
https://www.developerway.com/posts/replacing-react-with-css
Через CSS можно стилизовать дочерние элементы на основании родительского элемента:
.content .card {
background: #f0f0f0;
}
С помощью псевдокласса :has можно сделать наоборот, снизу вверх: стилизовать родительский элемент на основании дочернего элемента. Например, карточки, в которых есть изображение, имеют другой цвет границы:
.card:has(img) {
border-top: 10px solid #fee6ec;
}
Псевдокласс :has поддерживается всеми основными браузерами, глобально 92% пользователей поддерживают его.
С помощью псевдокласса :has можно сделать многие фичи без написания кода на React. В своем блоге Надя Макаревич рассказала несколько примеров использования :has, которые заменяют код на React. Например, стилизация карточки и соседних карточек при фокусе на кнопки действий внутри карточки. Пример CSS стилей:
// Все карточки после карточки с фокусом на кнопку «открытия»
.card:has([data-action='open']:focus-visible) ~ .card,
// Все карточки перед карточкой с фокусом на кнопку «открытия»
.card:has(~ .card [data-action='open']:focus-visible) {
filter: greyscale();
background-color: #f6f7f6;
}
// Стиль карточки с фокусом на кнопку «открытия»
.card:has([data-action='open']:focus-visible) {
transform: scale(1.02);
border-top: 10px solid #c3dccf;
}
https://www.developerway.com/posts/replacing-react-with-css
Developerway
Replacing React code with CSS :has selector
Looking into what the new CSS :has selector is and how it can be used to improve our React code. Includes practical and beautiful examples.
👍17
TanStack Form
Стейт менеджер формы от TanStack. Особенности:
🔴 Headless архитектура
🔴 Гранулированная реактивность
🔴 Поддержка сложных валидаций и обработок ошибок на уровне формы, поля
🔴 Поддержка через адаптеры для валидаций Zod, Yup, Valibot
🔴 Небольшой размер (5.2кБ gzip)
🔴 Поддержка вложенных объектов и массивов
Пример:
https://tanstack.com/form/latest/docs/overview
Стейт менеджер формы от TanStack. Особенности:
Пример:
import { useForm } from '@tanstack/react-form';
export default function App() {
const form = useForm({
defaultValues: {
firstName: '',
},
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value);
},
});
return (
<div>
<h1>Simple Form Example</h1>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<div>
<form.Field
name="firstName"
validators={{
onChangeAsyncDebounceMs: 500,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return (
value.includes('error') && 'No "error" allowed in first name'
);
},
}}
children={(field) => {
return (
<>
<label htmlFor={field.name}>First Name:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</>
);
}}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : 'Submit'}
</button>
)}
/>
</form>
</div>
);
}
https://tanstack.com/form/latest/docs/overview
Please open Telegram to view this post
VIEW IN TELEGRAM
Tanstack
Overview | TanStack Form Docs
TanStack Form is the ultimate solution for handling forms in web applications, providing a powerful and flexible approach to form management. Designed with first-class TypeScript support, headless UI...
🔥6👍2
Чистый код React с TypeScript
В своей статье Робин Везер объясняет, как правильно типизировать компоненты React, использовать встроенные типы React, обрабатывать пропсы и дочерние элементы, управлять стейтом с помощью хуков, работать с рефами и обрабатывать события.
Несколько примеров, которые привел автор статьи:
✨ Передача компонентов в пропсах. Можно указать требуемые пропсы у передаваемых компонентов, а определить компонент через генерик тип ComponentType:
✨ Для определения рефов через пропсы используйте генерик тип RefObject:
✨ MouseEvent и React.MouseEvent. MouseEvent - это нативное JavaScript событие, а React.MouseEvent - это синтетическое событие, которое используется в колбеках React. У них много общего, и их часто можно использовать как взаимозаменяемые, но версия React также учитывает несовместимость браузеров и включает некоторые специфичные для React методы, такие как persist().
https://weser.io/blog/clean-react-with-typescript
В своей статье Робин Везер объясняет, как правильно типизировать компоненты React, использовать встроенные типы React, обрабатывать пропсы и дочерние элементы, управлять стейтом с помощью хуков, работать с рефами и обрабатывать события.
Несколько примеров, которые привел автор статьи:
import { ComponentType } from 'react'
type ProductTileProps = {
title: string
description?: string
}
type Props = {
render: ComponentType<ProductTileProps>
}
function ProductTile({ render }: Props) {
const props = {…}
return render(props)
}
import { RefObject } from 'react'
type Props = {
anchor: RefObject<HTMLElement>
}
function Popover({ anchor }: Props) {
// position component according to the anchor ref
}
https://weser.io/blog/clean-react-with-typescript
Please open Telegram to view this post
VIEW IN TELEGRAM
Robin Weser's Blog
Clean React with TypeScript
Exploring common React features and patterns and how to seamlessly integrate them with TypeScript
👎11👍9❤1
Композиция компонентов в React
В своем блоге Доминик Дорфмайстер поделился, почему стоит предпочитать композицию компонентов и ранний возврат, а не условный рендеринг нескольких состояний. Рассмотрим пример «плохого» компонента:
Этот компонент плох тем, что в нем много внутренних условных рендеров, что усложняет чтение и понимание работы компонента. Если разобраться во внутренних условиях, то компонент выше можно разделить на три соответствующих состоянию запроса: во время загрузки, нет данных и есть данные. Чтобы отображать каждое состояние запроса по отдельности, используйте ранний возврат и композицию. Такой код будет проще и легче читать. Пример:
https://tkdodo.eu/blog/component-composition-is-great-btw
В своем блоге Доминик Дорфмайстер поделился, почему стоит предпочитать композицию компонентов и ранний возврат, а не условный рендеринг нескольких состояний. Рассмотрим пример «плохого» компонента:
function Layout(props: { children: ReactNode }) {
return (
<Card>
<CardHeading>Welcome 👋</CardHeading>
<CardContent>{props.children}</CardContent>
</Card>
)
}
export function ShoppingList() {
const { data, isPending } = useQuery(/* ... */)
return (
<Layout>
{data?.assignee ? <UserInfo {...data.assignee} /> : null}
{isPending ? <Skeleton /> : null}
{!data && !isPending ? <EmptyScreen /> : null}
{data
? data.content.map((item) => (
<ShoppingItem key={item.id} {...item} />
))
: null}
</Layout>
)
}
Этот компонент плох тем, что в нем много внутренних условных рендеров, что усложняет чтение и понимание работы компонента. Если разобраться во внутренних условиях, то компонент выше можно разделить на три соответствующих состоянию запроса: во время загрузки, нет данных и есть данные. Чтобы отображать каждое состояние запроса по отдельности, используйте ранний возврат и композицию. Такой код будет проще и легче читать. Пример:
export function ShoppingList() {
const { data, isPending } = useQuery(/* ... */)
if (isPending) {
return (
<Layout>
<Skeleton />
</Layout>
)
}
if (!data) {
return (
<Layout>
<EmptyScreen />
</Layout>
)
}
return (
<Layout>
{data.assignee ? <UserInfo {...data.assignee} /> : null}
{data.content.map((item) => (
<ShoppingItem key={item.id} {...item} />
))}
</Layout>
)
}
https://tkdodo.eu/blog/component-composition-is-great-btw
tkdodo.eu
Component Composition is great btw
Component composition is one of the best parts of React, and I think we should take more time to break our components into manageable parts before littering one component with conditional renderings.
👍28
Контроль компонентов через URL
Интерактивный пост в блоге как сделать поле поиска в таблице, чтобы запрос сохранялся в URL. В примере иллюстрируется распространенный анти-паттерн, когда пытаются синхронизировать стейт поиска и URL.
Правильный паттерн в таком примере использовать URL как источник истины и отказаться от стейта. Это связано с тем, что URL находится вне нашего контроля, и пользователь может сам его изменять. Поэтому использование стейта и useEffect для синхронизации будет приводить к багам. Пример использования URL как источника истины:
https://buildui.com/posts/how-to-control-a-react-component-with-the-url
Интерактивный пост в блоге как сделать поле поиска в таблице, чтобы запрос сохранялся в URL. В примере иллюстрируется распространенный анти-паттерн, когда пытаются синхронизировать стейт поиска и URL.
Правильный паттерн в таком примере использовать URL как источник истины и отказаться от стейта. Это связано с тем, что URL находится вне нашего контроля, и пользователь может сам его изменять. Поэтому использование стейта и useEffect для синхронизации будет приводить к багам. Пример использования URL как источника истины:
export default function Home() {
let searchParams = useSearchParams();
let search = searchParams.get('search') ?? '';
/* ... */
return (
<>
{/* ... */}
<Input
value={search}
onChange={(e) => {
let search = e.target.value;
if (search) {
router.push(`${pathname}?search=${search}`);
} else {
router.push(pathname);
}
}}
placeholder="Find someone..."
/>
</>
);
}
https://buildui.com/posts/how-to-control-a-react-component-with-the-url
Build UI
How to control a React component with the URL
👍22👎3
Релиз React DevTools 6.0
Вышел релиз React DevTools 6.0. Основные изменения были связаны с поддержкой серверных компонентов. Что нового:
🔴 Поддержка серверных компонентов в дереве.
🔴 Фильтрация компонентов по окружению. Можно исключить серверные/клиентские компоненты из дерева.
🔴 При выводе стека ошибки использует source map для быстрого перехода в компонент, где произошла ошибка.
🔴 Раскрыли функцию installHook, в которую можно передать настройки. Пригодится для авторов библиотек.
Браузерное расширение еще не было опубликовано, но ознакомиться со списком изменений подробнее и со скриншотами можно в changelog:
https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#600
Вышел релиз React DevTools 6.0. Основные изменения были связаны с поддержкой серверных компонентов. Что нового:
Браузерное расширение еще не было опубликовано, но ознакомиться со списком изменений подробнее и со скриншотами можно в changelog:
https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#600
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18
Негласные правила React хуков
При объявлении хука useEffect нужно указывать список зависимостей, массив переменных, которые используются внутри хука. Например:
В этом случае хук объявлен неправильно, переменная x не указана как зависимость. Однако, это правило не универсальное. Некоторые переменные являются стабильными, т.е. ссылка на них не меняется при ре-рендере, и их можно не указывать. Для того чтобы не ошибаться, нужно использовать плагин eslint-plugin-react-hooks. Например, какие переменные плагин считает стабильными:
В своем блоге автор высказывает мнение, что есть недостаток в этом подходе. Если использовать хуки из сторонних библиотек, то возвращаемые в них переменные всегда нужно указывать в зависимостях эффекта. Это может быть setter useAtom из Jotai или вызов функции useMutation из tanstack-query. По идее эти переменные тоже стабильные, но их придется указывать в зависимостях, иначе будет ошибка плагина eslint-plugin-react-hooks.
https://macwright.com/2024/09/19/the-extra-rules-of-hooks
При объявлении хука useEffect нужно указывать список зависимостей, массив переменных, которые используются внутри хука. Например:
const [x, setX] = useState(0);
useEffect(() => {
console.log(x);
}, []);
В этом случае хук объявлен неправильно, переменная x не указана как зависимость. Однако, это правило не универсальное. Некоторые переменные являются стабильными, т.е. ссылка на них не меняется при ре-рендере, и их можно не указывать. Для того чтобы не ошибаться, нужно использовать плагин eslint-plugin-react-hooks. Например, какие переменные плагин считает стабильными:
const [state, setState] = useState() / React.useState()
// ^^^ стабильная ссылка
const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ стабильная ссылка
const [state, dispatch] = useActionState() / React.useActionState()
// ^^^ стабильная ссылка
const ref = useRef()
// ^^^ стабильная ссылка
const onStuff = useEffectEvent(() => {})
// ^^^ стабильная ссылка
Остальные ссылки не стабильны
В своем блоге автор высказывает мнение, что есть недостаток в этом подходе. Если использовать хуки из сторонних библиотек, то возвращаемые в них переменные всегда нужно указывать в зависимостях эффекта. Это может быть setter useAtom из Jotai или вызов функции useMutation из tanstack-query. По идее эти переменные тоже стабильные, но их придется указывать в зависимостях, иначе будет ошибка плагина eslint-plugin-react-hooks.
https://macwright.com/2024/09/19/the-extra-rules-of-hooks
macwright.com
The unspoken rules of React hooks
👎36👍7
Оптимизация загрузки SPA с помощью предварительной загрузки чанков
Для уменьшения размера бандла используют подход разделения кода по роутам. В этом подходе при открытии или переключении страницы происходит ленивая загрузка чанка роута. Из-за ленивой загрузки чанка, парсинга и выполнения скрипта увеличивается время загрузки страницы. Это может быть особенно актуально при открытии сайта, в случае ленивой загрузки происходит «водопад» запросов – сначала загружается и исполняется основной бандл, который лениво загружает чанк текущей страницы.
Для решения проблемы водопада при открытии сайта можно вставлять небольшой скрипт в
В своей статье автор поделился скриптом для предварительной загрузки чанка. Для реализации автор использовал сборщик Rsbuild. Для именования чанков можно использовать комментарий webpackChunkName. Это пригодится для мапинга пути и имени чанка:
https://mmazzarolo.com/blog/2024-08-13-async-chunk-preloading-on-load/
Для уменьшения размера бандла используют подход разделения кода по роутам. В этом подходе при открытии или переключении страницы происходит ленивая загрузка чанка роута. Из-за ленивой загрузки чанка, парсинга и выполнения скрипта увеличивается время загрузки страницы. Это может быть особенно актуально при открытии сайта, в случае ленивой загрузки происходит «водопад» запросов – сначала загружается и исполняется основной бандл, который лениво загружает чанк текущей страницы.
Для решения проблемы водопада при открытии сайта можно вставлять небольшой скрипт в
<head>, который будет мапить текущий адрес и чанк роута. Найденный чанк роута может подгружаться через link rel="preload”. В своей статье автор поделился скриптом для предварительной загрузки чанка. Для реализации автор использовал сборщик Rsbuild. Для именования чанков можно использовать комментарий webpackChunkName. Это пригодится для мапинга пути и имени чанка:
const Home = lazy(
() => import(/* webpackChunkName: "home" */ "./pages/home-page"),
);
export const routeChunkMapping = {
"/": "home",
"/home": "home"
};
https://mmazzarolo.com/blog/2024-08-13-async-chunk-preloading-on-load/
Mmazzarolo
Optimizing SPA load times with async chunks preloading
Improve the performance of client-side rendered SPAs by avoiding the waterfall effect caused by route-based lazy-loading.
👍11❤3
Environment API в Vite
В Vite 6 появится Environment API, которое позволит использовать Vite в разных окружениях. Это нужная фича для React. Например, это позволит для React с серверными компонентами создавать разный бандл для клиента, сервера и RSC – т.е. 3 разных бандла под каждое окружение.
Кроме этого, Environment API позволит запускать приложения не только на Node.js, но и в других средах выполнения, таких как Deno, Bun, Edge Runtime, а также создавать для этих сред выполнения бандл.
Новое API – это низкоуровневое API, которое предназначено для разработчиков библиотек, и не предполагается, что его будут использовать пользователи напрямую.
https://green.sapphi.red/blog/increasing-vites-potential-with-the-environment-api
В Vite 6 появится Environment API, которое позволит использовать Vite в разных окружениях. Это нужная фича для React. Например, это позволит для React с серверными компонентами создавать разный бандл для клиента, сервера и RSC – т.е. 3 разных бандла под каждое окружение.
Кроме этого, Environment API позволит запускать приложения не только на Node.js, но и в других средах выполнения, таких как Deno, Bun, Edge Runtime, а также создавать для этих сред выполнения бандл.
Новое API – это низкоуровневое API, которое предназначено для разработчиков библиотек, и не предполагается, что его будут использовать пользователи напрямую.
https://green.sapphi.red/blog/increasing-vites-potential-with-the-environment-api
green.sapphi.red
Increasing Vite's potential with the Environment API
sapphi-red's portfolio.
👍20
This media is not supported in your browser
VIEW IN TELEGRAM
Drag to Select
Интерактивный гайд с иллюстрациями о том, как реализовать выбор элементов в React. В гайде учтены разные функции, например скроллинг во время перетаскивания, отмена всего выбора через Escape и уменьшение количества выбранных элементов.
https://www.joshuawootonn.com/react-drag-to-select
Интерактивный гайд с иллюстрациями о том, как реализовать выбор элементов в React. В гайде учтены разные функции, например скроллинг во время перетаскивания, отмена всего выбора через Escape и уменьшение количества выбранных элементов.
https://www.joshuawootonn.com/react-drag-to-select
👍36🔥5