Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(useCustomEnsuredControl/useFloatingWithInteractions): Initialize preservedControlledValueRef with value #7099

Conversation

mendrew
Copy link
Contributor

@mendrew mendrew commented Jun 28, 2024

Описание

Мы получили репорт об ошибке от пользователей:

TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')

shown в объекте используется только в useFloatingWithInteractions, который управляется с помощью хука useCustomEnsuredControl.
Единственный момент когда бы состояние могло быть undefined это момент когда вызывается callback

setShownLocalState((prevState) => {
if (prevState.shown !== nextShown || prevState.reason !== reason) {
return {
shown: nextShown,
reason,
};
}
/* istanbul ignore next: страховка, если вдруг на момент вызова обновления состояния, оно уже будет актуальным */
return prevState;
});

Тут мы вызываем setShownLocalState с функцией, у которой в аргументах может лежать предыдущее состояние стейта.
В этот момент у useCustomEnsuredControl, если компонент контролируемый и вызывает onChange с коллбэком, мы обращаемся к переменной preservedControlledValueRef.

} else if (onChangeProp) {
const resolvedValue = nextValue(preservedControlledValueRef.current);
onChangeProp(resolvedValue);
}

Эта переменная может быть undefined только при инициализации, до тех пор пока в value не будет передано значение !== undefined.

const preservedControlledValueRef = React.useRef<V>();
useIsomorphicLayoutEffect(() => {
preservedControlledValueRef.current = value;
});

Изменения

Инициализируем ref сразу же со значением value.

Воспроизведение

Мне никак не удалось довести компонент до состояния ошибки, ни локально, ни в тестовом окружении.
Возможно, что это вообще ложный след.
Возможно, что это также может происходить при изменении состояния компонента из неконтролируемого в контролируемое.

Потому что такая ошибка может возникнуть только если value был undefined, потом стал defined и был быстро вызван onChange , так быстро, что preservedControlledValueRef не успел обновится.

Возможно, что стектрейс, который привёл нас сюда не совсем верный. Его надо спрашивать в VK Teams.

We got an error from users about'shown' in
useFloatingWithInteractions hook

TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')

I wasn't able to catch this locally, or in test env, but
to make sure preservedControlledValueRef can't be undefined
if value is defind we give it a value.
Copy link

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@mendrew mendrew marked this pull request as ready for review June 28, 2024 17:44
@mendrew mendrew requested a review from a team as a code owner June 28, 2024 17:44
@mendrew mendrew self-assigned this Jun 28, 2024
@mendrew mendrew added this to the v6.2.1 milestone Jun 28, 2024
Copy link
Contributor

size-limit report 📦

Path Size
JS 368.65 KB (+0.01% 🔺)
JS (gzip) 112.87 KB (0%)
JS (brotli) 92.91 KB (+0.05% 🔺)
JS import Div (tree shaking) 1.42 KB (0%)
CSS 289.84 KB (0%)
CSS (gzip) 37.66 KB (0%)
CSS (brotli) 30.44 KB (0%)

Copy link
Contributor

e2e tests

Playwright Report

Copy link
Contributor

👀 Docs deployed

Commit 406784a

Copy link

codecov bot commented Jun 28, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 83.78%. Comparing base (7d4f104) to head (406784a).
Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #7099   +/-   ##
=======================================
  Coverage   83.78%   83.78%           
=======================================
  Files         357      357           
  Lines       10676    10676           
  Branches     3551     3551           
=======================================
  Hits         8945     8945           
  Misses       1731     1731           
Flag Coverage Δ
unittests 83.78% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@inomdzhon inomdzhon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅

@SevereCloud SevereCloud merged commit f49dd16 into master Jul 5, 2024
26 checks passed
@SevereCloud SevereCloud deleted the mendrew/fix/useCustomEnsuredContro/initialize-preservedControlledValueRef branch July 5, 2024 12:24
@inomdzhon inomdzhon added the patch Автоматизация: PR продублируется в ветку последнего минорного релиза для выпуска патча label Jul 8, 2024
BlackySoul pushed a commit that referenced this pull request Jul 8, 2024
We got an error from users about'shown' in
useFloatingWithInteractions hook

TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')

I wasn't able to catch this locally, or in test env, but
to make sure preservedControlledValueRef can't be undefined
if value is defind we give it a value.
@BlackySoul BlackySoul mentioned this pull request Jul 8, 2024
BlackySoul added a commit that referenced this pull request Jul 8, 2024
We got an error from users about'shown' in
useFloatingWithInteractions hook

TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')

I wasn't able to catch this locally, or in test env, but
to make sure preservedControlledValueRef can't be undefined
if value is defind we give it a value.

Co-authored-by: Andrey Medvedev <andr.medv.spb@gmail.com>
@vkcom-publisher
Copy link
Contributor

v6.2.1 🎉

mendrew added a commit that referenced this pull request Aug 22, 2024
… reveal prevValue undefined (#7285)

Выяснилось, что всё-таки, если передавать в свойство `onChange` `useCustomEnsuredControl` коллбэк `nextValue` c аргументом `prevValue`, то есть вероятность, что `prevValue` будет `undefined`.
Это видно, если исправить тип аргумента prevValue с `any` на `V`.
https://github.com/VKCOM/VKUI/blob/a7b79b3b3a69bdf68163f62521f68697aceb52c2/packages/vkui/src/hooks/useEnsuredControl.ts#L56

Дело в том, что `preservedControlledValueRef`, которое мы передаём в `nextValue` коллбэк как значение `prevValue`, может быть `undefined`, так как хранит предыдущее значение `value`, которое тоже может быть `undefined`.
https://github.com/VKCOM/VKUI/blob/a7b79b3b3a69bdf68163f62521f68697aceb52c2/packages/vkui/src/hooks/useEnsuredControl.ts#L70-L73

В теории (потому что в тестах не удалось повторить), если пользователь в `useCustomEnsuredControl` не которолирует значение `value` (то есть проп `value === undefined`, а мы храним значение `value` локально в `useCustomEnsuredControl`), а затем делает `value` котролируемым (то есть начинает передавать в проп `value` какое-то значение), то есть вероятность, что при вызове `onChanage` `preservedControlledValueRef` ещё не обновился и равен предыдущему значение `value`, то есть `undefined`, тогда пользователь получит `prevValue` со значением `undefined`, что может привести к ошибке, как описано в #7099
> TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')


## Изменения
- Исправили тип `prevValue` в колбэке `nextValue`, заменив `any`, чтобы была видна проблема c undefined.

Но теперь ясно, что мы не можем просто так передать `preservedControlledValueRef` в `nextValue` коллбэк в качестве `prevValue`, потому что типы не сходятся.

Что можно сделать.

1. Просто не вызывать `nextValue`, если `preservedControlledValueRef` `undefined`, но тогда мы получаем риск не отреагировать на действие пользователя, так как `onChange` не будет вызван и состояние не поменяется.

2. Разрешить `prevValue` по типам быть `undefined`. Но тогда пользователи, использующие в `onChange` коллбэк `nextValue` получат по типам вариант, что `prevValue` может быть `undefined`.
Это очень странное состояние и тяжело объяснить его причины. А также тяжело принять решение при обработке onChange.
Например, в `useChipsInput` не понятно откуда тогда взять актуальный массив `chips`, чтобы добавить в него новое значение.
https://github.com/VKCOM/VKUI/blob/7755cabb003c2dac894703ac30335c0b874d8d52/packages/vkui/src/components/ChipsInput/useChipsInput.ts#L107-L126

3. (Текущее решение) Если `preservedControlledValueRef` `undefined`, то использовать значение `value`.
Так как мы знаем, что компонент котролируемый, значит `value` имеет какое-то значение. При переходе из некоторолируемого значения в контролируемое у `value` нету предыдущего состояния, поэтому самым адекватным решением будет предать текущее значение `value` как `prevValue`.
Чтобы `value` не находилось в массиве зависимостей `onChange` и не тригерило новое значение `onChange` при изменении, мы `value` прячем в `ref`, и используем лишь тогда, когда `preservedControlledValueRef` === `undefined`.

- Добавили warning. Хотелось ещё дать ссылку на [React warning](https://react.dev/reference/react-dom/components/input#im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled), тогда надо пояснять, что в нашем случае речь не только про input, а как это сделать лаконично я не придумал.
vkcom-publisher pushed a commit that referenced this pull request Aug 22, 2024
… reveal prevValue undefined (#7285)

Выяснилось, что всё-таки, если передавать в свойство `onChange` `useCustomEnsuredControl` коллбэк `nextValue` c аргументом `prevValue`, то есть вероятность, что `prevValue` будет `undefined`.
Это видно, если исправить тип аргумента prevValue с `any` на `V`.
https://github.com/VKCOM/VKUI/blob/a7b79b3b3a69bdf68163f62521f68697aceb52c2/packages/vkui/src/hooks/useEnsuredControl.ts#L56

Дело в том, что `preservedControlledValueRef`, которое мы передаём в `nextValue` коллбэк как значение `prevValue`, может быть `undefined`, так как хранит предыдущее значение `value`, которое тоже может быть `undefined`.
https://github.com/VKCOM/VKUI/blob/a7b79b3b3a69bdf68163f62521f68697aceb52c2/packages/vkui/src/hooks/useEnsuredControl.ts#L70-L73

В теории (потому что в тестах не удалось повторить), если пользователь в `useCustomEnsuredControl` не которолирует значение `value` (то есть проп `value === undefined`, а мы храним значение `value` локально в `useCustomEnsuredControl`), а затем делает `value` котролируемым (то есть начинает передавать в проп `value` какое-то значение), то есть вероятность, что при вызове `onChanage` `preservedControlledValueRef` ещё не обновился и равен предыдущему значение `value`, то есть `undefined`, тогда пользователь получит `prevValue` со значением `undefined`, что может привести к ошибке, как описано в #7099
> TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')

## Изменения
- Исправили тип `prevValue` в колбэке `nextValue`, заменив `any`, чтобы была видна проблема c undefined.

Но теперь ясно, что мы не можем просто так передать `preservedControlledValueRef` в `nextValue` коллбэк в качестве `prevValue`, потому что типы не сходятся.

Что можно сделать.

1. Просто не вызывать `nextValue`, если `preservedControlledValueRef` `undefined`, но тогда мы получаем риск не отреагировать на действие пользователя, так как `onChange` не будет вызван и состояние не поменяется.

2. Разрешить `prevValue` по типам быть `undefined`. Но тогда пользователи, использующие в `onChange` коллбэк `nextValue` получат по типам вариант, что `prevValue` может быть `undefined`.
Это очень странное состояние и тяжело объяснить его причины. А также тяжело принять решение при обработке onChange.
Например, в `useChipsInput` не понятно откуда тогда взять актуальный массив `chips`, чтобы добавить в него новое значение.
https://github.com/VKCOM/VKUI/blob/7755cabb003c2dac894703ac30335c0b874d8d52/packages/vkui/src/components/ChipsInput/useChipsInput.ts#L107-L126

3. (Текущее решение) Если `preservedControlledValueRef` `undefined`, то использовать значение `value`.
Так как мы знаем, что компонент котролируемый, значит `value` имеет какое-то значение. При переходе из некоторолируемого значения в контролируемое у `value` нету предыдущего состояния, поэтому самым адекватным решением будет предать текущее значение `value` как `prevValue`.
Чтобы `value` не находилось в массиве зависимостей `onChange` и не тригерило новое значение `onChange` при изменении, мы `value` прячем в `ref`, и используем лишь тогда, когда `preservedControlledValueRef` === `undefined`.

- Добавили warning. Хотелось ещё дать ссылку на [React warning](https://react.dev/reference/react-dom/components/input#im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled), тогда надо пояснять, что в нашем случае речь не только про input, а как это сделать лаконично я не придумал.
andrey-medvedev-vk pushed a commit that referenced this pull request Sep 19, 2024
… reveal prevValue undefined (#7285)

Выяснилось, что всё-таки, если передавать в свойство `onChange` `useCustomEnsuredControl` коллбэк `nextValue` c аргументом `prevValue`, то есть вероятность, что `prevValue` будет `undefined`.
Это видно, если исправить тип аргумента prevValue с `any` на `V`.
https://github.com/VKCOM/VKUI/blob/a7b79b3b3a69bdf68163f62521f68697aceb52c2/packages/vkui/src/hooks/useEnsuredControl.ts#L56

Дело в том, что `preservedControlledValueRef`, которое мы передаём в `nextValue` коллбэк как значение `prevValue`, может быть `undefined`, так как хранит предыдущее значение `value`, которое тоже может быть `undefined`.
https://github.com/VKCOM/VKUI/blob/a7b79b3b3a69bdf68163f62521f68697aceb52c2/packages/vkui/src/hooks/useEnsuredControl.ts#L70-L73

В теории (потому что в тестах не удалось повторить), если пользователь в `useCustomEnsuredControl` не которолирует значение `value` (то есть проп `value === undefined`, а мы храним значение `value` локально в `useCustomEnsuredControl`), а затем делает `value` котролируемым (то есть начинает передавать в проп `value` какое-то значение), то есть вероятность, что при вызове `onChanage` `preservedControlledValueRef` ещё не обновился и равен предыдущему значение `value`, то есть `undefined`, тогда пользователь получит `prevValue` со значением `undefined`, что может привести к ошибке, как описано в #7099
> TypeError: Uncaught TypeError: Cannot read properties of undefined (reading 'shown')


## Изменения
- Исправили тип `prevValue` в колбэке `nextValue`, заменив `any`, чтобы была видна проблема c undefined.

Но теперь ясно, что мы не можем просто так передать `preservedControlledValueRef` в `nextValue` коллбэк в качестве `prevValue`, потому что типы не сходятся.

Что можно сделать.

1. Просто не вызывать `nextValue`, если `preservedControlledValueRef` `undefined`, но тогда мы получаем риск не отреагировать на действие пользователя, так как `onChange` не будет вызван и состояние не поменяется.

2. Разрешить `prevValue` по типам быть `undefined`. Но тогда пользователи, использующие в `onChange` коллбэк `nextValue` получат по типам вариант, что `prevValue` может быть `undefined`.
Это очень странное состояние и тяжело объяснить его причины. А также тяжело принять решение при обработке onChange.
Например, в `useChipsInput` не понятно откуда тогда взять актуальный массив `chips`, чтобы добавить в него новое значение.
https://github.com/VKCOM/VKUI/blob/7755cabb003c2dac894703ac30335c0b874d8d52/packages/vkui/src/components/ChipsInput/useChipsInput.ts#L107-L126

3. (Текущее решение) Если `preservedControlledValueRef` `undefined`, то использовать значение `value`.
Так как мы знаем, что компонент котролируемый, значит `value` имеет какое-то значение. При переходе из некоторолируемого значения в контролируемое у `value` нету предыдущего состояния, поэтому самым адекватным решением будет предать текущее значение `value` как `prevValue`.
Чтобы `value` не находилось в массиве зависимостей `onChange` и не тригерило новое значение `onChange` при изменении, мы `value` прячем в `ref`, и используем лишь тогда, когда `preservedControlledValueRef` === `undefined`.

- Добавили warning. Хотелось ещё дать ссылку на [React warning](https://react.dev/reference/react-dom/components/input#im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled), тогда надо пояснять, что в нашем случае речь не только про input, а как это сделать лаконично я не придумал.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
patch Автоматизация: PR продублируется в ветку последнего минорного релиза для выпуска патча type:bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants