Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

View alexeyraspopov's full-sized avatar
✍️
Working on something exciting

Oleksii alexeyraspopov

✍️
Working on something exciting
View GitHub Profile
@alexeyraspopov
alexeyraspopov / Patterns.md
Last active July 8, 2023 17:42
Небольшие полезные паттерны для React и хуков

Data Injection

Задача: компоненту необходимо получить сторонние данные, которые он не может получить через пропсы.

Проблема: разные источники данных могут иметь разные API, которые влекут за собой необходимость реализации дополнительных аспектов в рамках компонента: useState/useEffect, обработка loading state, доступ к асинхронным API, etc.

Решение: Каждый раз когда компоненту нужны сторонние данные, создавай

export default function transformer(file, api) {
const j = api.jscodeshift;
const {expression, statement, statements} = j.template;
return j(file.source)
.find(j.BinaryExpression, { operator: '==' })
.replaceWith(p => {
const {left, right} = p.value;
return expression`${left} === ${right}`;

This implementation makes use of useSyncExternalStore() that is available in React 18 or via shim.

Example

Store declaration:

// CounterStore.js
import { createStore } from 'thelib'
function useDebouncedValue(value, delay) {
let [debounced, setDebounced] = useState(value);
useEffect(() => {
let timer = setTimeout(setDebounced, delay, value);
return () => clearTimeout(timer);
}, [delay, value]);
return debounced;
}
import { useCallback, useMemo, useEffect, useSyncExternalStore } from "react";
import { BehaviorSubject, from, firstValueFrom } from "rxjs";
export function useSubjectQuery(query, deps) {
let subject$ = useMemo(() => new BehaviorSubject(), []);
useEffect(() => {
let subscription = from(query()).subscribe({
next: (value) => subject$.next(value),
error: (error) => subject$.error(error),
Maybe(13)
.bind(a => a * a)
.bind(log);
isWorthy("Loki")
.bind(null, () => Just("Vision"))
.bind(name => log(name, "is worthy"));
function isWorthy(name) {
return name === "Thor" ? Just(name) : Nothing();
/** ISC License (c) 2021 Alexey Raspopov */
import { useLayoutEffect, useRef, useState } from "react";
import cns from "./ResponsiveFrame.module.css"
export function ResponsiveFrame({ className, style, children }) {
let sceneRef = useRef();
let [width, height] = useElementSize(sceneRef);
return (
<div className={className} style={style} ref={sceneRef}>
<svg className={cns.svgFrame} width={width} height={height}>
function Observer() {
let head = { prev: null, next: null }
let tail = { prev: null, next: null }
head.next = tail
tail.prev = head
function subscribe(callback) {
let node = { callback, prev: tail.prev, next: tail }
tail.prev.next = node
tail.prev = node

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

Race Condition

Представим какую-то таб группу которая показывается таблицы с разными данными. При переключении между табами нужно загрузить и показать новую таблицу. Не вдаваясь в подробности, попробуем написать дата фетчинг по зову сердца и по первому попавшемуся примеру из документации Реакта:

function App() {
  let [query, setQuery] = useState(null);
  let [data, setData] = useState(null);

There is a common pattern to implement an observer/event emitter that returns a subscription handler when a new listener is created. This means subscription handler has direct access to the scope that create the listener. Usually, these observers use arrays or sets to store a list of listeners. Removing a listener means using deletion methods that an array or a set provides. Alternatively, the list of listeners can be implemented as a doubly-linked list. This makes both listener insert and delete operations O(1) instead of array/set's complexity. Obviously, there won't be significant performance gain, but no overhead is better than small overhead.