Заметки про React
3.82K subscribers
34 photos
8 videos
485 links
Короткие заметки про React.js, TypeScript и все что с ним связано

Контакт: @rimlin
Download Telegram
TanStack DB

TanStack представила новую библиотеку – TanStack DB – реактивный клиентский стор, с возможностью синка API запросов. Библиотека работает поверх TanStack Query и расширяет его функционал. Разработчики обещают быструю скорость работы библиотеки, даже на больших объемах данных.

Библиотека предлагает fine-grained реактивность, возможность нормализации данных и примитивы транзакций.

Примеры использования:

Синк данных коллекции с API:


import { createQueryCollection } from "@tanstack/db-collections"

const todoCollection = createQueryCollection<Todo>({
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 <List items={todos} />
}


Использование транзакций и оптимистичных изменений на клиенте:


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 (
<Button
onClick={() =>
addTodo.mutate(() =>
todoCollection.insert({
id: uuid(),
text: "🔥 Make app faster",
completed: false,
})
)
}
/>
)
}


https://github.com/TanStack/db
👍17🔥8👎51
Управление фокусом в React с помощью flushSync

Установить фокус на инпуте сразу в React может оказаться не так просто, как кажется. При изменении стейта, React не сразу ре-рендерит компонент, а вместо этого он складывает в очередь все изменения состояний и выполняет их разом после завершения работы обработчика событий. Из-за такого поведения могут возникнуть непредвиденные ситуации, например:


function MyComponent() {
const inputRef = useRef<HTMLInputElement>(null)
const [show, setShow] = useState(false)

return (
<div>
<button
onClick={() => {
setShow(true)
inputRef.current?.focus() // Фокус не будет работать
}}
>
Show
</button>
{show ? <input ref={inputRef} /> : null}
</div>
)
}


В примере выше фокс на инпуте не появится, из-за того что изменение стейта setShow сработает только по завершению работы обработчика события onClick.

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




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




https://www.epicreact.dev/mastering-focus-management-in-react-with-flush-sync-f5b38
👍292🔥2
Анонс 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/
👍24🔥42
Почему Error Boundary, а не просто try/catch для компонентов

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


const element = (
<div>
<h1>Calculator</h1>
<Calculator left={1} operator="+" right={2} />
<Calculator left={1} operator="-" right={2} />
</div>
)


Поэтому если обернуть объявление выше в try/catch можно получить только ошибки во время создания этих элементов, а не ошибки рендера. Реальные ошибки происходят внутри компонента, во время рендера, эффектов и обработчиков ошибок.


function Calculator(props) {
try {
// ...render logic
} catch (error) {
return <div>Ошибка!</div>
}
}


По примеру выше можно обернуть в 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 <button onClick={handleClick}>Do something</button>
}


https://www.epicreact.dev/why-react-error-boundaries-arent-just-try-catch-for-components-i6e2l
🔥13👍86
Остерегайтесь скрытых проблем при работе с 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 к высокочастотному инпуту, например <input type="text"> или <input type="range">.

Со временем схема стейта в URL может изменяться, т.е. могут переименовываться поля, удаляться или добавляться новые. Нужно поддержать возможность миграции на новую схему.

При изменении URL может происходить замена текущей истории или добавлении в историю нового URL. При добавлении URL в историю появляется возможность управлять историей через браузерные кнопки вперед/назад. Это удобно для пользователя, но добавляет сложность для разработки, т.к. появляется два источника управления историей: через UI и браузерные кнопки вперед/назад. Например, при открытии модального окна добавляется в историю ?modalOpen=true. Теперь пользователь ожидает, что при нажатии на браузерную кнопку назад закроет окно.

https://nuqs.47ng.com/blog/beware-the-url-type-safety-iceberg
👍111
Реактивность – это легко

Стандартный 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 (
<Context.Provider value={store}>
{Array.from({ length: 50 }).map((_, i) => (
<Cell index={i} />
))}
</Context.Provider>
);
}

const selectors = {
isFocus: (state, index) => state.focus === index,
};

function Cell({ index }) {
const store = useContext(Context);
const focus = useSelector(store, selectors.isFocus, index);

return (
<button
ref={ref}
onClick={() => store.update({ ...store.state, focus: index })}
className={clsx({ focus })}
>
{index}
</button>
);
};


Такой подход позволяет обновлять только те компоненты, чьи данные действительно изменились, и часто избавляет от необходимости оборачивать всё в React.iss.onemo.

https://romgrk.com/posts/reactivity-is-easy/
Please open Telegram to view this post
VIEW IN TELEGRAM
👎18👍51